Local Development with Docker
|
This content was generated with the assistance of AI and should be verified against the official Joomla documentation before being relied on in production. This sub-section’s Joomla bibliography lists the reference material consulted while preparing these pages. |
A Joomla site needs PHP, a web server and a database, and a throwaway local copy needs them to be easy to create, to
reset and to match production. The official joomla image on Docker Hub provides exactly that: Apache (or PHP-FPM),
PHP and the Joomla files, with an entrypoint that can install the site without opening a browser. This page, checked on
2026-10-30, runs the Tallpine Hiking Club site (package pkg_tallpine, component com_tallpine, child template
cassiopeia_tallpine) with Joomla 6.2 on PHP 8.4, MariaDB and Mailpit, then compares the alternatives (XAMPP, MAMP,
DDEV) and explains how to build Joomla core from its Git repository. It pins Joomla 6.2.x and PHP 8.3 or later, the
platform of the current release. For the container tooling itself see
Compose for local development; for requirements and hosting
see Requirements and Hosting.
The official image and its tags
The image is the Docker Official Image joomla (https://hub.docker.com/_/joomla). Its tags combine three choices.
| Choice | Values (Joomla 6.2 line) |
|---|---|
Joomla version |
|
PHP version |
|
Server variant |
|
So 6.2.0-php8.4-apache, 6.2-php8.5-fpm and 6.2.0-php8.3-fpm-alpine are all valid. Pin at least the minor and the
PHP version (6.2-php8.4-apache) in a project, and an exact patch (6.2.0-php8.4-apache) when reproducing a bug:
latest moves to the next major without warning. Match the PHP version to the one production will run; Joomla 6 needs
PHP 8.3 or later, which is why PHP 8.2 appears only on the 5.4 tags. The Alpine variant is attractive for size but its
musl libc differs from the Debian build that most hosts use, so prefer apache or fpm when the goal is production
parity.
Older books and tutorials build on Joomla 3 or 4 images and tags such as joomla:3.9-php7.4-apache. Those lines are
end of life (4.x since 2025-10). Developing Extensions for Joomla! 5 (2023) and Joomla! 4 Masterclass (2023) do not
cover Docker, and the platform they target is security-only or end of life.
Configuration variables
The entrypoint reads environment variables on first start. Database variables tell it where the database is; the auto-install variables let it complete the installer itself.
Database
| Variable | Meaning |
|---|---|
|
Host and optional port of the database server. Defaults to the linked |
|
Database user. Defaults to |
|
Database password. Defaults to the linked container’s |
|
Path of a file that contains the password (a Docker or Compose secret), as an alternative to the variable. |
|
Database name, |
|
|
Automatic installation
When the site variables are set the container installs Joomla on first start and the browser installer is skipped.
| Variable | Meaning |
|---|---|
|
The site name. |
|
The full name of the first Super User. |
|
The login name of the Super User. |
|
The Super User password. It must satisfy the installer’s password rules. |
|
The Super User e-mail address. |
|
Semicolon-separated URLs of extension packages to install during the set-up. |
|
Semicolon-separated paths, inside the container, of extension packages to install. |
|
Host and port of the outgoing mail server. These are the only two SMTP variables the image documents. |
JOOMLA_EXTENSIONS_PATHS is what makes the container useful for extension work: build pkg_tallpine.zip on the host,
mount the folder that contains it and name the file in the variable, and a fresh site starts with the package already
installed. The variables apply only when the site is first created; once the joomla_data volume holds a
configuration.php, changing them does nothing, and the site is reset by removing the volumes (see
Resetting and persistence).
The password variables are the weak spot. Environment variables are visible with docker inspect and in CI logs, so a
Compose file never contains a literal password: the values below come from an untracked .env file, and the Compose
interpolation form ${NAME:?message} makes docker compose stop with a message when one is missing.
JOOMLA_DB_PASSWORD_FILE with a Compose secret is the stronger option when the file is shared with other developers.
A Compose file with MariaDB and Mailpit
The stack has three services. joomla is the site, db is MariaDB and mailpit catches every e-mail the site sends,
so registration, password reset and notification mails can be tested without reaching a real mailbox.
First create the untracked .env file next to compose.yaml. The values are placeholders; generate your own, for
instance with openssl rand -base64 24, and add .env to .gitignore.
cat > .env <<'ENV'
TALLPINE_DB_PASSWORD=CHANGE_ME_DB
TALLPINE_DB_ROOT_PASSWORD=CHANGE_ME_ROOT
TALLPINE_ADMIN_PASSWORD=CHANGE_ME_ADMIN_AT_LEAST_12_CHARS
TALLPINE_ADMIN_EMAIL=admin@tallpine.test
ENV
echo ".env" >> .gitignore
Then the Compose file.
name: tallpine
services:
joomla:
image: joomla:6.2-php8.4-apache
restart: unless-stopped
depends_on:
db:
condition: service_healthy
ports:
- "8080:80"
environment:
JOOMLA_DB_HOST: db
JOOMLA_DB_USER: tallpine
JOOMLA_DB_PASSWORD: ${TALLPINE_DB_PASSWORD:?set it in .env}
JOOMLA_DB_NAME: tallpine
JOOMLA_SITE_NAME: Tallpine Hiking Club
JOOMLA_ADMIN_USER: Tallpine Admin
JOOMLA_ADMIN_USERNAME: tallpine-admin
JOOMLA_ADMIN_PASSWORD: ${TALLPINE_ADMIN_PASSWORD:?set it in .env}
JOOMLA_ADMIN_EMAIL: ${TALLPINE_ADMIN_EMAIL:?set it in .env}
JOOMLA_SMTP_HOST: mailpit
JOOMLA_SMTP_HOST_PORT: "1025"
JOOMLA_EXTENSIONS_PATHS: /packages/pkg_tallpine.zip
volumes:
- joomla_data:/var/www/html
- ./packages:/packages:ro
db:
image: mariadb:11.8
restart: unless-stopped
environment:
MARIADB_DATABASE: tallpine
MARIADB_USER: tallpine
MARIADB_PASSWORD: ${TALLPINE_DB_PASSWORD:?set it in .env}
MARIADB_ROOT_PASSWORD: ${TALLPINE_DB_ROOT_PASSWORD:?set it in .env}
volumes:
- db_data:/var/lib/mysql
healthcheck:
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
interval: 10s
timeout: 5s
retries: 10
mailpit:
image: axllent/mailpit:v1.27
restart: unless-stopped
ports:
- "8025:8025"
environment:
MP_SMTP_AUTH_ACCEPT_ANY: "1"
MP_SMTP_AUTH_ALLOW_INSECURE: "1"
volumes:
joomla_data:
db_data:
Source: Docker Hub: joomla (example Compose file and variables), Docker Hub: mariadb and Mailpit: Docker install
Start it and watch the first start, which creates the database tables and the Super User:
mkdir -p packages
docker compose up -d
docker compose logs -f joomla
When the log settles, the site is at http://localhost:8080, the administrator at http://localhost:8080/administrator
(log in with tallpine-admin and the password from .env) and every outgoing mail in the Mailpit inbox at
http://localhost:8025.
Points worth understanding in this file:
-
No published port for the database or for SMTP. Services reach each other by name on the Compose network, so only the two browser ports are published. Publishing
3306for a desktop database client is a convenience to add deliberately and to bind to loopback ("127.0.0.1:3306:3306"). -
depends_onwith a health check. Thedbservice reports healthy only when MariaDB accepts connections, so thejoomlaentrypoint does not race the database on a cold start. The official MariaDB image has no default health check; itshealthcheck.shscript is provided for this purpose. -
MariaDB, not MySQL. The Docker Hub example uses MySQL 8.0; both work, and the image variable is the same (
JOOMLA_DB_TYPEstaysmysqli). Use the database engine and major version that production runs, because Joomla does not support migrating between MySQL or MariaDB and PostgreSQL after the fact. -
Mailpit accepts anything.
MP_SMTP_AUTH_ACCEPT_ANYlets Joomla send with any or no credentials. Mailpit keeps the messages instead of delivering them, which is the point; it must not be reachable from a network you do not trust. -
Bind-mounted
packages. The package thatJOOMLA_EXTENSIONS_PATHSinstalls is built on the host and mounted read-only; the read-write Joomla files stay in the named volume.
After the first start, open System > Global Configuration > Server > Mail Settings and confirm that the mailer is SMTP
with host mailpit, port 1025, no authentication and no encryption, then use Send Test Mail. The mail templates
that produce the messages are covered in
Mail Templates and Email Settings.
PHP-FPM variants
With fpm or fpm-alpine the container runs PHP-FPM and serves no HTTP. A second container, for instance nginx, must
share the Joomla files (the same volume) and pass .php requests to the joomla service on the FastCGI port. The extra
moving parts only pay off when the goal is to rehearse a production nginx set-up, which is described in
Web Server, Reverse Proxy and HTTPS; for everyday development
the apache variant is simpler.
Working with the running container
# Joomla's command line interface inside the container
docker compose exec --user www-data joomla php cli/joomla.php list
docker compose exec --user www-data joomla php cli/joomla.php cache:clean
# A shell, and the container log
docker compose exec joomla bash
docker compose logs --tail=100 joomla
# A database shell (uses the container's own variables)
docker compose exec db sh -c 'mariadb -u"$MARIADB_USER" -p"$MARIADB_PASSWORD" "$MARIADB_DATABASE"'
Source: Joomla User Guide: Using the CLI
Run commands as the web server user (--user www-data) when they write files such as the cache, logs or
configuration.php; otherwise the files end up owned by root and the site can no longer write them. The command line
interface is covered in Command Line Interface.
Resetting and persistence
The Joomla files live in joomla_data and the database in db_data, so docker compose down keeps the site and
docker compose down -v deletes both volumes and returns to a clean first start, where the auto-install variables run
again. That is the fastest way to test an installer script or an install of pkg_tallpine from scratch. Because the
whole of /var/www/html is in the volume, the volume also holds the code of Joomla itself: pulling a newer image tag
does not upgrade an existing site. Update the site as in production, from the administrator, as described in
Updating Joomla and Automated Core Updates, or remove
the volumes and start again.
Developing an extension in the container
Two approaches work. The first, shown above, installs a built package. The second bind-mounts the source of a single extension over the installed one so edits show immediately:
services:
joomla:
volumes:
- joomla_data:/var/www/html
- ./com_tallpine/administrator:/var/www/html/administrator/components/com_tallpine
- ./com_tallpine/site:/var/www/html/components/com_tallpine
- ./com_tallpine/media:/var/www/html/media/com_tallpine
The component must already be installed once so that its database records and namespace map exist. If new classes or a
new extension are not picked up, delete administrator/cache/autoload_psr4.php in the container; Joomla regenerates it
on the next request. On macOS and Windows bind mounts are slower than a named volume, which is the reason to mount only
the extension and not the whole site.
Alternatives
| Tool | Notes |
|---|---|
XAMPP, WampServer, Bearsampp (Windows), MAMP (macOS) |
Bundled Apache, MariaDB or MySQL and PHP installed on the host. Quick for a single site, but with little isolation from the rest of the machine and, usually, one PHP version at a time. Check that the bundled PHP is 8.3 or later and enable the extensions the installer reports as missing. The Joomla Manual’s get-started page names WAMP, Bearsampp and XAMPP; the User Guide’s local-setup page adds Laragon, a Linux LAMP stack, Docker, a build from source and a Raspberry Pi set-up. |
Laragon (Windows) |
An all-in-one local server presented by the User Guide as a lightweight alternative to WAMP and XAMPP. |
DDEV |
A Docker-based tool with per-project PHP, database and web-server versions, HTTPS on |
The official image, as above |
The closest match to a container-based production deployment and the smallest set-up when only Joomla is needed. |
DDEV is the better choice when several projects with different PHP versions share a machine, or when the team already uses it for Drupal and WordPress. A DDEV set-up for a downloaded Joomla looks like this:
mkdir tallpine && cd tallpine
curl -fsSL -o joomla.zip "https://www.joomla.org/latest"
unzip joomla.zip && rm joomla.zip
ddev config --project-type=joomla
mkdir -p .ddev/php
printf 'display_errors = off\noutput_buffering = off\n' > .ddev/php/joomla.ini
ddev start
ddev launch /administrator
Finish the installation in the browser, or with the installation/joomla.php install command from the quickstart,
using the database host db with the user, password and database name db that DDEV provides for local use. Those
credentials are a local convention of DDEV and exist only inside the developer’s machine.
Working on Joomla core itself
Everything above installs a release package. To change Joomla itself, test a pull request or build the front-end assets
you need the Git repository. Composer and Node.js are required only for this (the Developer Manual says so on its
Composer and npm pages, which are still stubs; the source tree’s README and package.json are the working reference).
git clone https://github.com/joomla/joomla-cms.git
cd joomla-cms
git checkout 6.2-dev
composer install
npm ci
npm run build:dev
-
composer installinstalls the PHP dependencies. -
npm ciinstalls the exact versions inpackage-lock.json. Thepackage.jsonrequires Node.js 20.0.0 or later and npm 10.1.0 or later, andnpm cideletes and recreatesnode_modules, so it is also the fix for a broken one. -
npm run build:devcompiles the JavaScript and Sass sources with the development settings;npm run builduses the production settings, andnpm run watch:devrebuilds on every change. The scriptslint:jsandlint:cssrun the linters andcypress:runthe browser tests. -
The repository is not installable out of the box: its README points to the stable package or a nightly build for a working site. For development, serve the checkout with any of the set-ups above (the container with the checkout bind-mounted as
/var/www/htmlis one way) and run the installer. -
After pulling changes that add, delete or rename extensions, delete
administrator/cache/autoload_psr4.php.
Testing of extensions in this environment continues in Testing Extensions and the CI builds in Build and CI for Extensions.
Common errors and pitfalls
-
The installer starts although the variables are set. The auto-install applies to a new site. An existing
joomla_datavolume with an older site, or a half-finished install, keeps its state; reset withdocker compose down -v. -
"Database connection failed" on the first start. Usually the database was not ready, or the user and database were created with different credentials in the
dbservice than in theJOOMLA_DB_*variables. Both services read the same.envvariable here for that reason. ChangingMARIADB_PASSWORDafter the data directory exists has no effect. -
Mail is not arriving. Check the mailer under Global Configuration, use the Mailpit address
mailpit(notlocalhost) from inside the container, and make sure the mail is not waiting for a scheduled task that has not run. -
Files owned by root or not writable. Run commands with
--user www-data, and fix bind-mounted folders on Linux with a matching UID. -
Slow pages on macOS and Windows. Bind-mounting the whole site is slow; keep the site in a named volume.
-
Using
latest. It moves to the next major release. Pin the tag. -
Treating the local stack as production. The Compose file here publishes plain HTTP and uses throwaway credentials; production needs HTTPS, secrets, backups and separate database hosting, as covered in configuration.php and .htaccess and Security Hardening, MFA and Passkeys.
-
Following Joomla 3 or 4 material. Images, PHP versions, the
JPATH_PLATFORMguard and the legacyJ*classes in older tutorials are out of date for 6.x; see Where Older Books and Tutorials Are Outdated.