mlocati/docker-php-extension-installer: PHP extensions in official Docker images without the build mess
Easily install PHP extensions in Docker containers
At a glance
- What is it?
- A shell script that installs PHP extensions into the official php Docker images, resolves the APT/APK build dependencies itself, and removes them afterwards. It is a convenience layer, not a package manager, and its value depends on how much you dislike writing apt-get blocks by hand.
- Who is it for?
- Adopt it if your Dockerfiles already start from an official php image and you are tired of maintaining apt-get install and docker-php-ext-install pairs for every extension. Do not adopt it if you need a reproducible, fully pinned build with no network fetch at build time, or if you are not using the official php images at all, since the script targets those specifically.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository received new commits within the last day.
- What is it written in?
- Mainly Shell, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 30, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The apt-get block you keep copying between Dockerfiles
Installing a PHP extension in a container is rarely one command. The README's whole premise is that you first need the build toolchain and the library headers for that extension, then you compile or enable it, then you delete the toolchain so the layer does not carry a compiler forever. Doing this by hand for gd, xdebug, redis and zip produces a Dockerfile that is mostly package names, and every new extension means another round of guessing which -dev or -dev-equivalent package it needs.
The project is a single shell script that owns that loop. You give it extension names, and it works out the APT or APK packages, installs them, builds the extension, and then removes the packages that are no longer needed. The README states the target audience implicitly: it is for people using the official PHP Docker images, Debian-based since jessie (Debian 8, minimum PHP 5.5) and Alpine-based since Alpine 3.9 (minimum PHP 7.1). If your base image is not one of those, the script's package logic has nothing to attach to.
What the script actually does between your RUN line and the final image
The mechanism is deliberately boring, which is the point. The script detects the base distribution, maps each requested extension to the packages it needs, installs those packages, invokes the PHP build tooling, and then reverses the package installation so the build-only dependencies do not survive into the image. The README's framing is explicit that the cleanup is part of the design: "at the end of the script execution, the no-more needed packages will be removed so that the image will be much smaller."
Extension selection is more than a list of names. The README documents version suffixes, so install-php-extensions xdebug-2.9.7 pins an exact version, and a caret prefix resolves the newest compatible one: xdebug-^2 or xdebug-^2.8. Because caret resolution can land on a prerelease, the README adds a stability suffix, so xdebug-^3@stable picks the most recent stable 3.x. Valid suffixes listed are @snapshot, @devel, @alpha, @beta and @stable. Separately, a bare state suffix such as xdebug-beta or mongodb-stable makes the script prefer that release state from PECL.
There is also a source path. If an extension is not in the curated set, or you need a specific commit, you can point the script at a GitHub repository with a ref (php-memcached-dev/[email protected], @master, a full or short SHA-1, or a refs/tags or refs/heads form) or at a URL returning a source archive. The README notes this requires the source to ship a package.xml or package2.xml, so it is not a general escape hatch for arbitrary tarballs.
Installing it and installing gd plus xdebug
The README gives several ways to get the script into the image. The shortest uses ADD with a mode flag, which pulls the latest release binary directly into the image and then runs it.
FROM php:7.2-cli
ADD --chmod=0755 https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions /usr/local/bin/
RUN install-php-extensions gd xdebugAfter the build, the gd and xdebug extensions should be present in the image. If you prefer not to rely on ADD's remote-fetch behaviour, the curl variant downloads the same release asset to /usr/local/bin, marks it executable, and runs it in the same layer.
FROM php:7.2-cli
RUN curl -sSLf \
-o /usr/local/bin/install-php-extensions \
https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions && \
chmod +x /usr/local/bin/install-php-extensions && \
install-php-extensions gd xdebugA third option avoids downloading the script at all by copying it out of a published image, either from GitHub Container Registry or Docker Hub. The README warns that this route can use an outdated copy unless you pull the image first.
FROM php:8.4-cli
COPY --from=ghcr.io/mlocati/php-extension-installer /usr/bin/install-php-extensions /usr/local/bin/
RUN install-php-extensions gd xdebugFor a one-off check rather than a Dockerfile, the README also shows piping the script straight into a shell with the extension names as arguments, using php:8.2-cli as the base. All of these examples pull from releases/latest/download or a floating image tag, so none of them pins the installer itself.
Where this stops being the right tool
The script is a convenience wrapper around distro packages and PECL, and it inherits their failure modes. It needs network access at build time to fetch packages and, for source installs, archives. If your build environment is offline or you require every artifact to come from an internal mirror, the script's package resolution is the wrong layer to fight; you would be better off writing the apt-get and docker-php-ext-install lines yourself and controlling the sources list.
Reproducibility is the second boundary. The README's own examples fetch the installer from releases/latest/download, which means two builds of the same Dockerfile on different days can run different installer versions. You can pin by copying from a tagged image or by downloading a specific release asset, but the README does not present pinning as the default path, and it does not document a rollback story if a new installer release breaks a build. That is a real operational cost, not a theoretical one, given the release cadence.
The third boundary is base image support. Debian since jessie and Alpine since 3.9 with PHP 7.1 or newer covers the official images, but a distroless or custom-built PHP image is outside the script's assumptions. And the source-install path requires package.xml or package2.xml, so a plain GitHub repository without PECL packaging metadata is not covered.
Compared with doing it by hand and with docker-php-ext-install
The obvious alternative is the manual approach: run apt-get install for the -dev packages, call docker-php-ext-install or pecl install, then apt-get purge and clean up. That is what most PHP Dockerfiles looked like before this script existed, and it is still what you write when you need to control exactly which packages land in which layer. The difference is not capability, it is where the knowledge lives: by hand, you carry the package mapping for every extension in your head or in a comment; with the script, that mapping is maintained in the repository's data directory and exercised by the project's test workflows.
A second alternative is to skip the official images and use a distribution's PHP packages, or a prebuilt image that already bundles the extensions you want. Those remove the build step entirely, at the cost of tracking that image's PHP version and extension set rather than the official php tags. The installer's advantage is that it stays on the official images, so your base is the same one everyone else uses and the PHP version is whatever tag you chose.
A third comparison is Composer's platform requirements versus the container. Composer will tell you an extension is missing, but it will not install it; the installer is the layer that satisfies that requirement before composer install runs. The two are complementary, not competing.
Maintenance, licence and what an upgrade actually costs
The repository is not archived, and the last push was on 2026-09-22, with releases 2.11.30, 2.11.29 and 2.11.28 all dated 2026-09-22. That is a high release cadence for a shell script, which is consistent with the project's job: PHP extensions and PECL packages move, and the package mappings have to move with them. The practical consequence is that an unpinned install is a moving target, and a pinned install needs periodic attention to pick up new extension support.
Upgrade cost is mostly in your Dockerfile, not in the script. If you copy from a floating image tag, rebuilding picks up whatever is current, and there is no changelog review step unless you go looking. If you pin, you own the bump. The README does not document a deprecation or migration policy for extension names or suffix syntax, so treat the suffix grammar (@stable, @beta, caret ranges) as an interface you should test rather than assume is frozen.
The licence is MIT, stated in the repository's LICENSE file and in the Dockerfile's org.opencontainers.image.licenses label. MIT is permissive and imposes no copyleft obligation on your Dockerfile or image. Whether you need to ship the licence text with a redistributed image is a question for your own legal review, not something this article can settle.
Editorial conclusion
Adopt it if your Dockerfiles already start from an official php image and you are tired of maintaining apt-get install and docker-php-ext-install pairs for every extension. Do not adopt it if you need a reproducible, fully pinned build with no network fetch at build time, or if you are not using the official php images at all, since the script targets those specifically. Before committing, verify which version of the script your build actually pulls (the ADD and curl examples use releases/latest/download, which is not pinned), check that your target extension is handled by the script rather than only by PECL, and confirm whether the maintainers' test matrix covers the PHP and Alpine or Debian combination you are building on.
Frequently asked questions
How do I install PHP extensions in Docker with mlocati/docker-php-extension-installer?
Add the script to an official php image and run it with the extension names as arguments, for example install-php-extensions gd xdebug. The README shows several ways to get the script in place: ADD from the releases URL, curl to /usr/local/bin, or COPY from the ghcr.io/mlocati/php-extension-installer or mlocati/php-extension-installer image.
What does PHP extension mean in the context of this installer?
It is a module that adds functionality to PHP, such as gd for image handling or xdebug for debugging, and the installer's job is to bring in the APT or APK packages needed to build it and then remove them. The README lists the supported bases as Debian since jessie (minimum PHP 5.5) and Alpine since 3.9 (minimum PHP 7.1).
Can I install a specific version of an extension with this script?
Yes. Appending -<version> pins an exact release, as in install-php-extensions xdebug-2.9.7, and a caret prefix resolves the newest compatible version, as in xdebug-^2 or xdebug-^2.8. The README notes that caret resolution can pick an unstable release, so you can append @stable, and the valid suffixes it lists are @snapshot, @devel, @alpha, @beta and @stable.
Can mlocati/docker-php-extension-installer build an extension that is not in its list?
The README documents installing from source by pointing at a GitHub repository with a ref, such as php-memcached-dev/[email protected] or a commit SHA-1, or at a URL returning a source archive. It states the source must include a package.xml or package2.xml file, so a repository without that packaging metadata is not covered.
Does the installer pin the version of the script itself?
Not by default. The README's ADD and curl examples fetch from releases/latest/download, and the COPY examples use a floating image tag, so the script version can change between builds. The README warns that the COPY route may use an outdated image unless you pull it first.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/mlocati-docker-php-extension-installer)