Drupal in Docker containers for production

Running Drupal in Docker for production means building one immutable image from a Composer codebase, keeping files and secrets outside it, and rebuilding it on each security release. This page covers the decisions, setup and operating model.

On this page

Why teams run Drupal in Docker containers for production

A team that must ship Drupal through a repeatable pipeline into infrastructure it controls needs the same artifact to move from build to test to production without manual edits on each server. Docker answers that need by packaging the Composer-built Drupal codebase into an image, while the database, the uploaded files and the environment-specific settings stay outside it. Containers fit better than hand-managed servers when several environments must match, when rollback should mean redeploying a previous image, and when security releases must be applied on a predictable cycle. Drupal is licensed under GPL-2.0-or-later and there is no paid edition of Drupal itself (Drupal.org licensing), so the cost of this work is engineering effort, not software licensing.

The base image has to follow the PHP requirements of the Drupal version you run. Drupal 11 is the current major version, and Drupal 10 reaches end of life on December 9, 2026. Drupal 10 requires PHP 8.1 or later, Drupal 11 requires PHP 8.3 or later and Drupal 12 requires PHP 8.5 or later, so the PHP minor version pinned in the image is a version decision you make before the build starts. Confirm the exact PHP and extension list against the Drupal PHP requirements page for your target release, and confirm your database client and web server choices against the system requirements before you write the Dockerfile.

The effort in this approach sits in the boundaries you draw. Decide who owns the base image rebuild, how it is tested against your modules, and how quickly it is rebuilt when Drupal publishes a security release on the third Wednesday of the month. Ask your team to confirm which contributed modules need PHP extensions or system packages, because those change the image, and which secrets and file paths the containers must receive at runtime. Those answers drive the image build pipeline, the registry and the release runbook, and they determine how much of the work is one-time setup and how much is ongoing rebuilding.

Container layout options and when each fits

This table compares the main ways to package a Composer-built Drupal codebase into images, from a single all-in-one image to separate web and PHP containers and in-house versus community base images. Read each row for the fit, the main trade-off and what your team must confirm before choosing. The main takeaway is that the web server and database choices decide much of the image design, so settle them first.

Layout optionBest fitMain trade-offWhat to confirm
Single all-in-one image (PHP-FPM with Nginx or Apache in one container)Small estates, single sites, teams that want one artifact to build, scan and roll backWeb server, PHP and process supervision share one lifecycle, so scaling and restarts are coarserWeb server choice (Apache or Nginx) and how the image starts both processes
Separate web and PHP containersTeams that scale the web tier and PHP-FPM pools independently, or run on KubernetesTwo images to version and deploy together, plus a socket or network path to keep correctShared file mounts, PHP-FPM pool settings and the path that serves static files
Separate web and PHP containers with the web server image from a maintained vendor baseSites that want fewer patches to the web server layerVendor image tags and config layout can change, so the Drupal config must be retested on each updateWhich web server image and tag policy the platform team will follow
In-house base image built from a minimal OS and pinned PHPOrganizations with strict image provenance rules and their own patch pipelineYour team owns PHP extensions, OS patches and rebuild cadence for every base updateWho rebuilds the base, how often, and how a security fix reaches all images
Community base image (maintained by a third party)Faster start with standard PHP extensions and sensible defaultsLess control over extensions and build layers, and supply chain review is still requiredImage source, maintainer activity, signed tags and whether it supports your PHP version
Database outside the imageMost production sites, with MariaDB, MySQL or PostgreSQL run as managed or separate servicesConnection settings and credentials must be injected per environmentDatabase engine and version, and how settings.php reads connection details at runtime
SQLite inside a container volumeDemos, tests or low-stakes internal sites onlyData lives in a volume that must be backed up and is hard to scale or shareWhether the Drupal release and contributed modules you need support SQLite
Drupal base image with Composer build in a separate stageTeams that keep build tools out of the runtime imageMulti-stage builds add pipeline steps and need cache disciplineWhich PHP version and extensions the runtime image must include

How to build the Drupal image pipeline step by step

This sequence turns a Composer-managed Drupal codebase into versioned, repeatable container images in which code, files and environment settings each stay in their own place. Each step closes with a check so the pipeline can be trusted before it reaches a production cluster.

  1. Lock the Composer codebase and dependencies

    Start in the Git repository. Confirm that composer.json and composer.lock are committed, that core and contributed modules are pinned to exact versions, and that the vendor directory and the sites/default/files directory are excluded from version control. The platform team or the Drupal developer owns this step. Done means a clean checkout runs composer install with the lock file and produces the same tree on two separate machines. Record the Drupal core version you are pinning so the rebuild plan can be checked against the Drupal core release schedule later.

  2. Write a multi-stage Dockerfile

    Create a build stage that runs composer install with the --no-dev flag, using a PHP image version that meets the PHP requirement for your Drupal core release. Copy the resulting vendor directory and web root into a slim runtime stage that includes only the PHP extensions and web server your site needs. Keep Composer, Git and build tools out of the runtime stage. Check the result by listing the final image's filesystem and confirming that no dev packages, .git directory or Composer cache are present.

  3. Copy only code into the image

    Add a .dockerignore file that excludes sites/*/files, private file paths, local settings files containing secrets, .env files, test fixtures and database dumps. The files directory must never be baked into the image, because it holds user uploads and changes at runtime. Verify by building the image and running a filesystem search inside the container for sites/default/files. The expected result is that the path is either absent or an empty mount point created at runtime, with no uploaded content inside the image layers.

  4. Mount the files directory and private storage

    Define the public and private file locations as volumes or object storage paths supplied by the platform, and point Drupal's file system settings at those locations through environment-specific configuration. The operator who runs the cluster or container host owns the storage class or bucket and its backup policy. Verify that a file uploaded in one container is readable from a second container running the same image, and that the directory permissions let the PHP process write to the public files path but not to the code tree.

  5. Read settings from environment-specific includes and secrets

    Keep settings.php in the image as a thin loader that includes a file chosen by an environment variable, for example a per-environment settings file for development, staging or production. Inject database credentials, hash salt and other secrets from the orchestrator's secret store as environment variables, not from files in the repository. Set trusted_host_patterns separately for each environment so that each hostname is explicitly allowed. Check by starting the same image in staging and production and confirming that each environment resolves its own database host and accepts only its own hostnames.

  6. Run cron and database updates as separate jobs

    Do not run drush updatedb, configuration import and cron inside the web container's startup command. Model each as its own job or deployment step that runs once per release, after the new image is available and before traffic moves to it. Confirm that the web containers start without performing schema changes and that the update job exits successfully and leaves the site in a status with no pending updates. Record the job logs with the image tag so that each release can be traced to its database changes.

  7. Plan image rebuilds from the release schedule and advisories

    Set a rebuild routine that follows the Drupal core release schedule, which states that bugfix releases come out on the first Wednesday of each month and security releases on the third Wednesday. Subscribe the security owner to advisories from the Drupal Security Team, because a contributed module or core advisory may require an immediate rebuild outside the routine. Each rebuild should regenerate the lock file from the reviewed update, rerun the image checks above and redeploy through the same job sequence. Done means the team can name the image tag running in production and the advisory or release that triggered its last rebuild.

Separating code, files and settings in a container

The image should be immutable: it carries the Composer-built Drupal codebase and nothing that changes per environment or per visitor. Uploaded content lives in the public and private file paths, which must sit on storage that outlives any container, and the database holds everything else. Drupal's documented settings behavior makes this workable. The settings.php file can load an environment-specific include, such as settings.local.php or a file your build mounts at a known path, and that include can read database credentials, the hash salt and file paths from environment variables supplied by your orchestrator. Confirm with your platform team which volume types, object storage options and secret stores your environment provides, because the answer decides whether public and private files can be shared across replicas and how backups reach them.

Two patterns deliver the runtime settings. Mounting a settings file from a secret or config volume keeps the image identical across environments and makes the file easy to review, but the mounted file still has to be managed and rotated. Generating the settings file at container start from environment variables keeps values out of the filesystem, but the startup script becomes code you must test and version. Either way, never write credentials, the hash salt or a database password into an image layer with COPY, ENV or a build argument. Layers persist in the registry and can be read by anyone with pull access, even if a later layer deletes the file. Scan built images for secrets in your pipeline and confirm who can pull from your registry before the first production release.

Environment-specific settings across development, staging and production

Local development usually runs on a single developer machine or a Compose stack with a throwaway database. Settings here favor speed and visibility over lockdown, so debugging output, verbose logging and relaxed cache settings are normal. The settings file should still load environment-specific values from outside the image, so a developer can point the same image at a local database without editing code.

  • Database host: points to a local container service name, with credentials supplied through environment variables or a local, git-ignored override file.
  • trusted_host_patterns: lists localhost and the local ports or hostnames the developer uses, so host header checks still run without blocking local access.
  • File paths: mounts the public and private file directories to local volumes, which keeps uploads out of the image layers.
  • Cache and logging: may disable aggressive page caching and enable verbose logging, but these choices must never be copied into staging or production.
  • Configuration split: often uses a development split that enables development modules, which should never be enabled in shared environments.

Before relying on this setup, confirm with your Drupal version which settings files are loaded by default and which override files the project expects.

Rebuilding images for security releases without drift

Drupal publishes security releases on the third Wednesday of the month and bugfix releases on the first Wednesday, so image rebuilds should follow that calendar and the advisory dates. Each rebuild must refresh the Composer lock, the base image, the test run and the deployment steps together, or the running containers drift from what was reviewed.

Trigger rebuilds on the release calendar

Set the rebuild pipeline to run on the third Wednesday for security releases and the first Wednesday for bugfix releases, and also when the Drupal Security Team publishes an advisory for a contributed module you use. Subscribe the platform team to those advisories so an out-of-cycle fix does not wait for the next scheduled build. Confirm the release dates against the core schedule each quarter, because a fixed cron rule will not catch an emergency release.

Refresh the Composer lock deliberately

A rebuild that runs composer update without review can pull in unrelated package changes. Regenerate the lock file for the Drupal core and security packages, commit the diff, and compare it against the previous lock before the image is built. Keep composer.json constraints pinned to the supported Drupal minor line so a rebuild cannot silently jump to a new major version.

Rebuild the base image, not just the code

The PHP and web server base image carries its own operating system packages and PHP extensions. Pull a fresh tag from the registry on every rebuild, and pin it by digest in the deployment manifest so the same image is promoted through staging and production. Check the PHP version against the Drupal version in use, since Drupal 11 requires PHP 8.3 or later and Drupal 12 requires PHP 8.5 or later.

Rerun the full test suite

Run functional tests, the Drupal update path checks and a smoke test against a copy of the production database before the image is promoted. Include the contributed modules in the run, because a core security fix can break a module that has not yet released a compatible version. If a contributed module lags, decide whether to hold the release, patch it, or accept a documented exception with a date to revisit.

Run database updates and configuration import

A new image does not change the database. The deployment job must run database updates and configuration import in a defined order before traffic reaches the new containers, and it must fail loudly if either step errors. Keep the database backup taken immediately before the deploy so a failed update can be reverted to the previous image and database state together.

Detect drift between environments

Drift appears when staging runs a digest that production does not, or when a container was patched by hand. Record the image digest, Composer lock hash and core version in each environment, and alert when they differ from the promoted release. Ask your team to confirm who approves an out-of-band patch and how it is merged back into the repository, so the next rebuild does not reverse it.

Questions teams ask before running Drupal in Docker

What drives the cost of containerizing a Drupal site?

The cost depends mostly on how far the current codebase is from a clean, Composer-built state and how many environments need to be reproduced. Teams pay for discovery of the existing site, a Dockerfile and image build pipeline, separation of code from files and settings, and handover documentation. The main cost multipliers are custom or contributed modules that rely on writable paths, hand-edited core files, multisite layouts, and the number of environments and registries in scope. Ongoing costs are the people who rebuild images, rotate base images and run releases. Ask any provider for a written scope that lists each environment, each deliverable and who maintains the pipeline after handover. No figure is given here because cost depends on the codebase and the environment.

How long does a first Drupal container pipeline usually take?

Duration is set by the state of the codebase, the number of environments and the rules your organization applies to build, registry and deployment. The work usually falls into a few phases: auditing the repository and Composer lock file, writing the image and build configuration, moving files and secrets out of the repository, wiring the pipeline to your registry and running it in staging. Teams with clean Composer projects and an existing registry move faster. Teams that need change approval, air-gapped package mirrors or a signed-image policy add time. Ask for milestones with named exit criteria in the written scope rather than a single end date.

What must our team provide before a containerization project starts?

Most of the dependency sits with the client. A typical list includes:

  • Read access to the Drupal repository, including the composer.json and composer.lock files and the custom code
  • Access to staging and production environments, or a copy of their configuration, so the container matches what runs today
  • A decision on where persistent files live, such as a shared volume or object storage, and access to that storage
  • The secrets management tool the organization already uses, with a named owner for database credentials and the hash salt
  • A container registry, a deployment target and named release owners who approve changes and security rebuilds

Confirm these items in the scope before work begins, because missing access is one of the most common causes of delay.

Can an in-house team containerize Drupal without outside help?

Yes, a team with Drupal, Composer and container experience can do this internally, and many do. The questions to answer honestly are whether anyone has built a production image before, whether the team can test the upgrade path for each Drupal release, and whether there is capacity to own the pipeline after launch. Drupal needs a settings file per environment, file storage outside the image and a deploy step that runs database updates and configuration import. If those are new skills, an outside partner can plan and build the first version, document it and hand over. DeployOpen implements, documents and supports self-hosted platforms in your infrastructure, so the choice between your team and a partner should rest on skills, capacity and the written scope.

How do image rebuilds fit the Drupal security release calendar?

Security releases for Drupal core and contributed projects follow a fixed rhythm. According to the Drupal core release schedule, bugfix releases come out on the first Wednesday of each month and security releases on the third Wednesday. Your pipeline should therefore be able to rebuild an image from the same lock file with an updated Drupal or module version within a short window after each security release, and it should be tested in staging before production. Plan who approves emergency rebuilds, how base PHP and web server images are refreshed, and how old images are retired. Confirm the process in your written scope and with your release owners.

Which Drupal and PHP versions should a new container image target?

Pick the Drupal major version first, then the PHP version it requires. According to the Drupal.org PHP requirements page, Drupal 10 requires PHP 8.1 or later, Drupal 11 requires PHP 8.3 or later and Drupal 12 requires PHP 8.5 or later. The Drupal core release schedule lists Drupal 11 as the current major version and states that Drupal 10 reaches end of life on December 9, 2026. A new image should therefore not target a version that will reach end of life soon after launch. Sites still on Drupal 7 cannot be containerized as a simple update, because security support ended on January 5, 2025, and they must be migrated first. Ask your team to confirm the target version in writing.

Does the container approach affect licensing or vendor lock-in?

Drupal and the contributed files hosted on Drupal.org that are derivative works of Drupal are licensed under GPL-2.0-or-later, according to the Drupal.org licensing page, and there is no paid edition of Drupal itself. Containerizing Drupal does not change that license. Your own custom modules and themes carry the license you choose, and some third-party or commercial modules and base images have their own terms. Review the license of every image layer and every module you add, and confirm with your legal or procurement team which terms apply to your build.

How to verify the container setup before go-live

Verification comes down to proving that the image behaves the same way in every environment and that nothing writes outside the paths you planned. Start by running the built image on its own, outside the cluster, and confirm that Drupal boots with the PHP version the image declares. Drupal 10 requires PHP 8.1 or later, Drupal 11 requires PHP 8.3 or later, and Drupal 12 requires PHP 8.5 or later, according to the Drupal.org PHP requirements page. Record the PHP and extension versions from the running container, not from the Dockerfile, so the evidence matches what actually ships.

Next, open the status report at /admin/reports/status in a container started with production-like settings and confirm there are no environment errors, such as unwritable directories, missing PHP extensions or a database driver mismatch. Then test the file boundary. Upload a file, generate image styles and trigger a cron run, and confirm that new writes land only in the mounted public and private file paths. Inspect the container's writable layer for stray files under the web root. Finally, send requests with an unknown Host header and confirm that trusted_host_patterns rejects them, and that the expected hostname for each environment is accepted.

Repeat the same tests after every security rebuild on a new base image, since a changed base can alter PHP, OpenSSL or library versions without any change to your code. Keep the test script in the repository so the same checks run in CI and before each promotion. Ask DeployOpen, or your own team, to deliver the test scripts, the recorded outputs for each image digest, a list of the checks that failed and how each was resolved, and a short runbook describing who repeats the checks after a Drupal security release. These records are evidence for your own review. They show what was tested and when. They are not an audit report, and an independent auditor will decide what they mean for your compliance program.

How DeployOpen scopes and delivers Drupal container work

DeployOpen plans, builds, documents and supports self-hosted open-source platforms inside your own infrastructure, including Drupal container images that run in your cloud accounts, data centers or air-gapped networks. Engagements are fixed-scope, with scope, deliverables and timelines agreed in writing before work starts.

How we scope

  • Written scope lists the Drupal version, PHP version and database engine your containers will run.
  • Scope records which image registry, build pipeline and runtime platform your team already operates.
  • Scope defines how settings, secrets, private files and environment configuration are separated from the image.

How we build

  • Composer-built codebase is packaged into images with a pinned base image and locked dependencies.
  • Environment-specific settings.php includes are wired so one image runs in development, staging and production.
  • Image rebuild process is tested so a Drupal security release can be applied through the same pipeline.

How we hand over

  • Handover covers Dockerfiles, build scripts, environment variable reference and runbooks for your team.
  • Deliverables include a documented procedure for rebuilding and redeploying images after a security advisory.
  • Your engineers run a rehearsal of build, deploy and rollback before the engagement closes.

How we support

  • Ongoing support, upgrades and continuous testing are available as a retainer after handover.
  • Retainer work can cover base image updates, Drupal release upgrades and review of security advisories.
  • Ask for the support boundaries, response arrangements and change process in the written quote.

Talk to our team.

Tell us what you're working on, whether it's a deployment, an audit, a security test or a cyber range. You'll speak with an engineer who can help you scope it.

  • 30-minute call: free, with no obligation.
  • NDA on request: we can sign before you share details.
  • Clear next steps: a scope and plan after the call.