github.com/ddev/ddev@v1.23.2-0.20240519125000-d824ffe36ff3/docs/content/users/extend/customizing-images.md (about) 1 # Customizing Docker Images 2 3 It’s common to have a requirement for the `web` or `db` images which isn’t bundled with them by default. There are two ways to extend these Docker images: 4 5 1. `webimage_extra_packages` and `dbimage_extra_packages` in `.ddev/config.yaml`. 6 2. An add-on Dockerfile in your project’s `.ddev/web-build` or `.ddev/db-build`. 7 8 ## Adding Extra Debian Packages with `webimage_extra_packages` and `dbimage_extra_packages` 9 10 You can add extra Debian packages with lines like this in `.ddev/config.yaml`: 11 12 ```yaml 13 webimage_extra_packages: ["php${DDEV_PHP_VERSION}-yaml", "php${DDEV_PHP_VERSION}-tidy"] 14 dbimage_extra_packages: [telnet, netcat, sudo] 15 ``` 16 17 Then the additional packages will be built into the containers during [`ddev start`](../usage/commands.md#start). 18 19 ## Adding PHP Extensions 20 21 ### PHP Extensions supported by `deb.sury.org` 22 23 If a PHP extension is supported by the upstream package management from `deb.sury.org`, you'll be able to add it with minimal effort. Test to see if it's available using `ddev exec 'sudo apt update && sudo apt install php${DDEV_PHP_VERSION}-<extension>'`, for example, `ddev exec 'sudo apt update && sudo apt install php${DDEV_PHP_VERSION}-imap'`. If that works, then the extension is supported, and you can add `webimage_extra_packages: ["php${DDEV_PHP_VERSION}-<extension>"]` to your `.ddev/config.yaml` file. 24 25 ### PECL PHP Extensions not supported by `deb.sury.org` 26 27 !!!tip "Few people need pecl extensions" 28 Most people don't need to install PHP extensions that aren't supported by `deb.sury.org`, so you only need to go down this path if you have very particular needs. 29 30 If a PHP extension is not supported by the upstream package management from `deb.sury.org`, you'll install it via pecl using a `.ddev/web-build/Dockerfile`. You can search for the extension on [pecl.php.net](https://pecl.php.net/) to find the package name. (This technique can also be used to get newer versions of PHP extensions than are available in the `deb.sury.org` distribution.) 31 32 For example, a `.ddev/web-build/Dockerfile.mcrypt` might look like this: 33 34 ```dockerfile 35 ENV extension=mcrypt 36 SHELL ["/bin/bash", "-c"] 37 # Install the needed development packages 38 RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y -o Dpkg::Options::="--force-confnew" --no-install-recommends --no-install-suggests build-essential php-pear php${DDEV_PHP_VERSION}-dev 39 # mcrypt happens to require libmcrypt-dev 40 RUN apt install -y libmcrypt-dev 41 RUN pecl install ${extension} 42 RUN echo "extension=${extension}.so" > /etc/php/${DDEV_PHP_VERSION}/mods-available/${extension}.ini && chmod 666 /etc/php/${DDEV_PHP_VERSION}/mods-available/${extension}.ini 43 RUN phpenmod ${extension} 44 ``` 45 46 A `.ddev/web-build/Dockerfile.xlswriter` to add `xlswriter` might be: 47 48 ```dockerfile 49 ENV extension=xlswriter 50 SHELL ["/bin/bash", "-c"] 51 # Install the needed development packages 52 RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y -o Dpkg::Options::="--force-confnew" --no-install-recommends --no-install-suggests build-essential php-pear php${DDEV_PHP_VERSION}-dev 53 # xlswriter requires libz-dev 54 RUN sudo apt install -y libz-dev 55 RUN echo | pecl install ${extension} 56 RUN echo "extension=${extension}.so" > /etc/php/${DDEV_PHP_VERSION}/mods-available/${extension}.ini && chmod 666 /etc/php/${DDEV_PHP_VERSION}/mods-available/${extension}.ini 57 RUN phpenmod ${extension} 58 59 ``` 60 61 A `.ddev/web-build/Dockerfile.xdebug` (overriding the `deb.sury.org` version) might look like this: 62 63 ```dockerfile 64 # This example installs xdebug from pecl instead of the standard deb.sury.org package 65 ENV extension=xdebug 66 SHELL ["/bin/bash", "-c"] 67 RUN phpdismod xdebug 68 # Install the needed development packages 69 RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y -o Dpkg::Options::="--force-confnew" --no-install-recommends --no-install-suggests build-essential php-pear php${DDEV_PHP_VERSION}-dev 70 # Remove the standard xdebug provided by deb.sury.org 71 RUN apt remove php${DDEV_PHP_VERSION}-xdebug || true 72 RUN pecl install ${extension} 73 # Use the standard xdebug.ini from source 74 ADD https://raw.githubusercontent.com/ddev/ddev/master/containers/ddev-php-base/ddev-php-files/etc/php/8.2/mods-available/xdebug.ini /etc/php/${DDEV_PHP_VERSION}/mods-available 75 RUN chmod 666 /etc/php/${DDEV_PHP_VERSION}/mods-available/xdebug.ini 76 # ddev xdebug handles enabling module so we don't enable here 77 #RUN phpenmod ${extension} 78 ``` 79 80 ## Determining What Packages You Need 81 82 The `web` container is a Debian image, and its PHP distributions are packaged (thank you!) by [`deb.sury.org`](https://deb.sury.org/). 83 84 Most PHP extensions are built within the `deb.sury.org` distribution. You can Google the extension you want, or download and search the [Packages](https://packages.sury.org/php/dists/buster/main/binary-amd64/Packages) list from the `sury` distribution. For example, the `bcmath` PHP extension is provided by `php-bcmath`. Many packages have version-specific names, like `php7.3-tidy`. 85 86 If you need a package that is *not* a PHP package, you can view and search standard Debian packages at [packages.debian.org/stable](https://packages.debian.org/stable/), or use Google. 87 88 To test that a package will do what you want, you can [`ddev ssh`](../usage/commands.md#ssh) and `sudo apt-get update && sudo apt-get install <package>` to verify that you can install it and you get what you need. A PHP extension may require `killall -USR2 php-fpm` to take effect. After you’ve tried that, you can add the package to [`webimage_extra_packages`](../configuration/config.md#webimage_extra_packages). 89 90 ## Adding Extra Dockerfiles for `webimage` and `dbimage` 91 92 For more complex requirements, you can add: 93 94 * `.ddev/web-build/Dockerfile` 95 * `.ddev/web-build/Dockerfile.*` 96 * `.ddev/db-build/Dockerfile` 97 * `.ddev/db-build/Dockerfile.*` 98 99 These files’ content will be inserted into the constructed Dockerfile for each image. They are inserted *after* most of the rest of the things that are done to build the image, and are done in alphabetical order, so `Dockerfile` is inserted first, followed by `Dockerfile.*` in alphabetical order. 100 101 For certain use cases, you might need to add directives very early on the Dockerfile like proxy settings or SSL termination. You can use `pre.` variants for this that are inserted *before* everything else: 102 103 * `.ddev/web-build/pre.Dockerfile.*` 104 * `.ddev/db-build/pre.Dockerfile.*` 105 106 Examine the resultant generated Dockerfile (which you will never edit directly), at `.ddev/.webimageBuild/Dockerfile`. You can force a rebuild with [`ddev debug refresh`](../usage/commands.md#debug-refresh). 107 108 Examples of possible Dockerfiles are `.ddev/web-build/Dockerfile.example` and `.ddev/db-build/Dockerfile.example`, created in your project when you run [`ddev config`](../usage/commands.md#config). 109 110 You can use the `.ddev/*-build` directory as the Docker “context” directory as well. So for example, if a file named `README.txt` exists in `.ddev/web-build`, you can use `ADD README.txt /` in the Dockerfile. 111 112 An example web image `.ddev/web-build/Dockerfile` might be: 113 114 ```dockerfile 115 RUN npm install -g gatsby-cli 116 ``` 117 118 Another example would be installing `phpcs` globally (see [Stack Overflow answer](https://stackoverflow.com/questions/61870801/add-global-phpcs-and-drupal-coder-to-ddev-in-custom-dockerfile/61870802#61870802)): 119 120 ```dockerfile 121 ENV COMPOSER_HOME=/usr/local/composer 122 123 # We try to avoid relying on Composer to download global, so in `phpcs` case we can use the PHAR. 124 RUN curl -L https://squizlabs.github.io/PHP_CodeSniffer/phpcs.phar -o /usr/local/bin/phpcs && chmod +x /usr/local/bin/phpcs 125 RUN curl -L https://squizlabs.github.io/PHP_CodeSniffer/phpcbf.phar -o /usr/local/bin/phpcbf && chmod +x /usr/local/bin/phpcbf 126 127 # If however we need to download a package, we use `cgr` for that. 128 RUN composer global require consolidation/cgr 129 RUN $COMPOSER_HOME/vendor/bin/cgr drupal/coder:^8.3.1 130 RUN $COMPOSER_HOME/vendor/bin/cgr dealerdirect/phpcodesniffer-composer-installer 131 132 # Register Drupal’s code sniffer rules. 133 RUN phpcs --config-set installed_paths $COMPOSER_HOME/global/drupal/coder/vendor/drupal/coder/coder_sniffer --verbose 134 # Make Codesniffer config file writable for ordinary users in container. 135 RUN chmod 666 /usr/local/bin/CodeSniffer.conf 136 # Make `COMPOSER_HOME` writable if regular users need to use it. 137 RUN chmod -R ugo+rw $COMPOSER_HOME 138 # Now turn it off, because ordinary users will want to be using the default. 139 ENV COMPOSER_HOME="" 140 ``` 141 142 **Remember that the Dockerfile is building a Docker image that will be used later with DDEV.** At the time the Dockerfile is executing, your code is not mounted and the container is not running, the image is being built. So for example, an `npm install` in `/var/www/html` will not do anything to your project because the code is not there at image building time. 143 144 ### Build Time Environment Variables 145 146 The following environment variables are available for the web Dockerfile to use at build time: 147 148 * `$BASE_IMAGE`: the base image, like `ddev/ddev-webserver:v1.21.4` 149 * `$username`: the username inferred from your host-side username 150 * `$uid`: the user ID inferred from your host-side user ID 151 * `$gid`: the group ID inferred from your host-side group ID 152 * `$DDEV_PHP_VERSION`: the PHP version declared in your project configuration (provided in versions after v1.21.4) 153 154 For example, a Dockerfile might want to build an extension for the configured PHP version like this: 155 156 ```Dockerfile 157 ENV extension=xhprof 158 ENV extension_repo=https://github.com/longxinH/xhprof 159 ENV extension_version=v2.3.8 160 161 RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y -o Dpkg::Options::="--force-confnew" --no-install-recommends --no-install-suggests autoconf build-essential libc-dev php-pear php${DDEV_PHP_VERSION}-dev pkg-config zlib1g-dev 162 RUN mkdir -p /tmp/php-${extension} && cd /tmp/php-${extension} && git clone ${extension_repo} . 163 WORKDIR /tmp/php-${extension}/extension 164 RUN git checkout ${extension_version} 165 RUN phpize 166 RUN ./configure 167 RUN make install 168 RUN echo "extension=${extension}.so" > /etc/php/${DDEV_PHP_VERSION}/mods-available/${extension}.ini 169 ``` 170 171 ## Installing into the home directory 172 173 The in-container home directory is rebuilt when you run `ddev restart`, so if you have something that installs into the home directory (like `~/.cache`) you'll want to switch users in the Dockerfile. In this example, `npx playwright install` installs a number of things into `~/.cache`, so we'll switch to the proper user before executing it, and switch back to the `root` user after installation to avoid surprises with any other Dockerfile that may follow. 174 175 ```Dockerfile 176 USER $username 177 # This is an example of creating a file in the home directory 178 RUN touch ~/${username}-was-here 179 # `npx playwright` installs lots of things in ~/.cache 180 RUN npx playwright install 181 RUN npx playwright install-deps 182 USER root 183 ``` 184 185 ### Debugging the Dockerfile Build 186 187 It can be complicated to figure out what’s going on when building a Dockerfile, and even more complicated when you’re seeing it go by as part of [`ddev start`](../usage/commands.md#start). 188 189 1. Use [`ddev ssh`](../usage/commands.md#ssh) first of all to pioneer the steps you want to take. You can do all the things you need to do there and see if it works. If you’re doing something that affects PHP, you may need to `sudo killall -USR2 php-fpm` for it to take effect. 190 2. Put the steps you pioneered into `.ddev/web-build/Dockerfile` as above. 191 3. If you can’t figure out what’s failing or why, running `ddev debug refresh` will show the full output of the build process. You can also run `export DDEV_VERBOSE=true && ddev start` to see what’s happening during the `ddev start` Dockerfile build.