Apps / Docker Images
Drupal 11 in Docker.
Enterprise CMS, pinned.
Drupal is a flexible open source content management system for building content-rich sites, portals, and web applications, with a strong taxonomy, fine-grained permissions, and a large module and theme ecosystem.
docker pull ghcr.io/remarkablecloud/drupal Current build
11.4.6-r1
Updated Sep 11, 2026
Base
Drupal 11 official (Apache), digest-pinned
What's inside
MariaDB pairing, trusted-host and proxy config, healthcheck
Upstream license
Drupal: GPL-2.0-or-later
Free to pull and run anywhere. Digest-pinned, updated deliberately; each build is recorded in the changelog.
The image
What our image adds.
Drupal is a flexible open source content management system for building content-rich sites, portals, and web applications, with a strong taxonomy, fine-grained permissions, and a large module and theme ecosystem.
Digest-pinned base
Built from `drupal@sha256:4249d58e94b052d5601499c7d1377433b5c9a8ad7b7b2d330368ce29181aca9c` (Drupal 11.4.6, PHP 8.5, Apache, Debian 13). Pinning by digest keeps rebuilds reproducible.
Headless install with drush
We add `drush` and run a first-boot `drush site:install` from environment variables, so Drupal installs with no web installer step and is ready to use immediately.
Admin password generated, never baked
If no password is supplied, the image generates a strong one on first install and prints it once to the container log. You can pin your own with `DRUPAL_ADMIN_PASSWORD`.
Proxy-aware
The install writes `reverse_proxy` and `trusted_host_patterns` into `settings.php`, so Drupal generates correct HTTPS URLs and serves cleanly behind a TLS-terminating reverse proxy without redirect loops.
Retag-safe layout
Drupal core, contrib modules, and vendor libraries live in the image; only `sites/default` (your `settings.php` and uploaded files) is persisted. Pulling a new image tag updates the platform without touching your content or database.
Healthcheck
A static `/rc-health.txt`, served directly by Apache and independent of Drupal routing, backs the container `HEALTHCHECK` so the platform routes traffic only once Drupal is serving. The current published tag is `ghcr.io/remarkablecloud/drupal:11.4.6-r1`. Drupa
Rather skip the compose file? Deploy Drupal with one click on your own VPS: TLS, domain and database already wired.
Deploy Drupal in one clickThe guide
Run it in production.
Architecture at a glance
- Drupal (Apache and PHP) serves plain HTTP on port
80. TLS is terminated upstream by a reverse proxy (Traefik on the RemarkableCloud App Platform). SetDRUPAL_TRUSTED_HOSTto the public hostname so Drupal accepts requests for it. - MariaDB is a separate database container and holds all content and configuration.
- A data volume at
/opt/drupal/web/sites/defaultholdssettings.phpand uploaded files. Core and modules stay in the image.
Docker Compose walkthrough
The standalone stack (Drupal and MariaDB) is defined in the image’s docker-compose.yml:
name: drupal
services:
db:
image: mariadb:11
restart: unless-stopped
environment:
MARIADB_DATABASE: drupal
MARIADB_USER: drupal
MARIADB_PASSWORD: ${DB_PASSWORD:-change-me-db}
MARIADB_ROOT_PASSWORD: ${DB_ROOT_PASSWORD:-change-me-root}
volumes: [db-data:/var/lib/mysql]
healthcheck:
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
interval: 10s
timeout: 5s
retries: 12
drupal:
image: ${DRUPAL_IMAGE:-ghcr.io/remarkablecloud/drupal:11.4.6-r1}
restart: unless-stopped
depends_on:
db:
condition: service_healthy
ports: ["${HTTP_PORT:-8097}:80"]
environment:
DRUPAL_DB_HOST: db
DRUPAL_DB_PORT: "3306"
DRUPAL_DB_NAME: drupal
DRUPAL_DB_USER: drupal
DRUPAL_DB_PASSWORD: ${DB_PASSWORD:-change-me-db}
DRUPAL_ADMIN_USER: ${DRUPAL_ADMIN_USER:-admin}
DRUPAL_ADMIN_PASSWORD: ${DRUPAL_ADMIN_PASSWORD:-}
DRUPAL_ADMIN_EMAIL: ${DRUPAL_ADMIN_EMAIL:-admin@example.com}
DRUPAL_SITE_NAME: ${DRUPAL_SITE_NAME:-Drupal}
DRUPAL_TRUSTED_HOST: ${DRUPAL_TRUSTED_HOST:-}
volumes:
- drupal-default:/opt/drupal/web/sites/default
volumes:
db-data:
drupal-default:
Step by step:
- The app waits for the database.
depends_onwithcondition: service_healthyavoids a first-boot race against MariaDB. - Install is headless. The
DRUPAL_DB_*variables drivedrush site:install; no web wizard. - The admin password is generated if blank. Leave
DRUPAL_ADMIN_PASSWORDempty and the image creates a strong one on first install and logs it once. - Only the web port is exposed.
${HTTP_PORT:-8097}:80is for local use; in production the proxy connects to port80on the internal network. - State lives in volumes.
drupal-default(sites/default) anddb-datapersist across restarts and upgrades.
To start it locally:
DB_PASSWORD=$(openssl rand -hex 16) docker compose up -d
Then browse to http://localhost:8097 and read the admin login from docker compose logs drupal. For production, set DRUPAL_TRUSTED_HOST to your public hostname and put the container behind a TLS-terminating reverse proxy.
Environment variable reference
These are wired automatically by the App Platform backend from the managed MariaDB; they are listed here for standalone and self-hosted runs.
| Variable | Required | Value / default | Purpose |
|---|---|---|---|
DRUPAL_DB_HOST | yes | <db_host> | MariaDB host. |
DRUPAL_DB_PORT | no | 3306 | MariaDB port. |
DRUPAL_DB_NAME | yes | <db_name> | Database name. |
DRUPAL_DB_USER | yes | <db_user> | Database user. |
DRUPAL_DB_PASSWORD | yes | <db_password> | Database password. |
DRUPAL_TRUSTED_HOST | no | <hostname> | Public hostname; written to trusted_host_patterns. |
DRUPAL_ADMIN_USER | no | admin | Admin login created at install. |
DRUPAL_ADMIN_PASSWORD | no | (generated) | Admin password. Leave empty to auto-generate on first install (printed once); set it to pin your own. |
DRUPAL_ADMIN_EMAIL | no | admin@example.com | Admin email. |
DRUPAL_SITE_NAME | no | Drupal | Site name. |
Only the first install reads these; afterwards changes happen inside Drupal.
Hardening notes
- No baked credentials. The admin password is generated at first install or supplied by you, never stored in an image layer.
- Digest-pinned base. Each build is auditable and reproducible.
- Healthcheck gates traffic.
/rc-health.txtmust return success before the platform routes requests. - Trusted host patterns. Set
DRUPAL_TRUSTED_HOSTso Drupal only answers for your hostname. - Keep MariaDB private. Do not publish its port to the host or the internet.
- After install, review roles and permissions, keep Drupal and modules updated, and consider making
settings.phpread-only.
Backups
Capture the database and the data volume together:
- The MariaDB database:
docker compose exec db mariadb-dump -u root -p"$DB_ROOT_PASSWORD" drupal > drupal-db.sql - The data volume at
sites/default(settings.phpand uploaded files):docker run --rm -v drupal_drupal-default:/data -v "$PWD":/backup alpine \ tar czf /backup/drupal-default.tgz -C /data .
On the RemarkableCloud App Platform, database and volume backups follow the platform’s backup schedule.
Upgrades
- New builds ship as new
-rtags. Because core and modules live in the image and onlysites/defaultis persisted, pulling a new tag updates Drupal core and modules; Drupal runs any database updates it needs:docker compose pull drupal && docker compose up -d drupal # then run database updates: docker compose exec drupal drush --root=/opt/drupal/web updatedb -y - Manage modules with composer (an image rebuild), rather than the UI, so retag upgrades stay clean.
- Back up first, and keep the database dump and the
sites/defaultarchive from just before the upgrade. - Read the changelog. Every build is listed in
CHANGELOG.mdwith the base image digest and any behavior changes.
Or skip the ops
Host Drupal on your own private VPS.
One click installs this exact image on a VPS that is yours alone, domain and TLS wizard included. The same server runs as many apps as fit, with no per-app charge.
FAQ
Questions we get.
Do I need a database?
Yes. Drupal stores content and configuration in MariaDB (or MySQL). The standalone compose file includes MariaDB.
Where does the admin password come from?
If you set `DRUPAL_ADMIN_PASSWORD`, that value is used. If you leave it empty, the image generates a strong one on first install and prints it once to the container log.
How does HTTPS work if the container only serves HTTP on port 80?
TLS is terminated at the reverse proxy (Traefik on the App Platform). The image sets `reverse_proxy` in `settings.php`, so Drupal honors `X-Forwarded-Proto` and generates https URLs. Set `DRUPAL_TRUSTED_HOST` to your hostname.
Can I add modules?
Yes, via composer as part of an image build, which keeps the retag-safe layout. UI-installed modules would live outside the persisted volume and be lost on a retag.
Can I use MySQL instead of MariaDB?
Yes. Drupal connects with the MySQL driver, which works with both MySQL 8 and MariaDB; point `DRUPAL_DB_HOST` at your server.
Your server runs. You sleep.
Fully managed hosting from people who have been doing this since 2001.