ToolsOps

Docker Compose for WordPress + MariaDB

WordPress with MariaDB in Docker Compose for local development: two volumes (database and files with your uploads), .env.example, a MariaDB healthcheck and how to fix the database connection error.

Spinning up WordPress locally is one of the things Docker Compose shines at: in a couple of minutes you have WordPress and its database without touching your system. This guide sets up WordPress with MariaDB and focuses on two things people regret ignoring: the volumes and the database connection error.

compose.yaml for WordPress + MariaDB

services:
  wordpress:
    image: wordpress:6-php8.3-apache
    restart: unless-stopped
    ports:
      - "127.0.0.1:8080:80"
    environment:
      WORDPRESS_DB_HOST: "mariadb:3306"
      WORDPRESS_DB_NAME: ${MARIADB_DATABASE}
      WORDPRESS_DB_USER: ${MARIADB_USER}
      WORDPRESS_DB_PASSWORD: ${MARIADB_PASSWORD}
    volumes:
      - wordpress_data:/var/www/html
    depends_on:
      mariadb:
        condition: service_healthy
  mariadb:
    image: mariadb:11
    restart: unless-stopped
    environment:
      MARIADB_DATABASE: ${MARIADB_DATABASE}
      MARIADB_USER: ${MARIADB_USER}
      MARIADB_PASSWORD: ${MARIADB_PASSWORD}
      MARIADB_ROOT_PASSWORD: ${MARIADB_ROOT_PASSWORD}
    volumes:
      - mariadb_data:/var/lib/mysql
    healthcheck:
      test: ["CMD-SHELL", "healthcheck.sh --connect --innodb_initialized"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 20s

volumes:
  wordpress_data:
  mariadb_data:

The .env.example:

MARIADB_DATABASE=wordpress
MARIADB_USER=wordpress
MARIADB_PASSWORD=CHANGE_ME
MARIADB_ROOT_PASSWORD=CHANGE_ME

Bring it up with docker compose up -d and open http://localhost:8080 for the WordPress installer.

Two volumes, two things to persist

It is easy to remember the database volume and forget the WordPress one. You need both:

  • mariadb_data stores the database: posts, pages, users, options and settings.
  • wordpress_data stores the files in /var/www/html: the WordPress core, the plugins and themes you install and, above all, your uploads in wp-content/uploads.

If you were missing wordpress_data, you would lose uploaded images even though the database stayed intact: posts would point at files that no longer exist.

"Error establishing a database connection"

It is the scariest message and it is almost always one of these three causes:

  • Wrong host. WORDPRESS_DB_HOST must be mariadb:3306, the service name, not localhost.
  • Mismatched credentials.WordPress's user, password and database name must be the same as MariaDB's. That is why both services read the same variables from the .env.
  • MariaDB was not ready yet. MariaDB's healthcheck (healthcheck.sh --connect --innodb_initialized) plus depends_on: condition: service_healthy make WordPress wait until the database accepts connections, which avoids the failure on first startup.

Restart and start fresh

docker compose ps                 # are they healthy?
docker compose logs -f wordpress  # installer logs
docker compose down               # stop (data safe)
docker compose down -v            # delete DB and WordPress files

Remember that down -v here removes both volumes: you lose the database and your uploads. That is what you want for a clean reinstall, not for routine maintenance.

Local, not production

This stack is for developing and testing on your machine. It is not a hardened deployment: a WordPress facing the Internet needs HTTPS, backups of both the database and the uploads, strong passwords outside git, and constant updates of WordPress and its plugins. Treat it as a disposable test site.

Next steps

Generate this stack in the Docker Compose generator and head back to the examples index to see other stacks.

Frequently asked questions

I get 'Error establishing a database connection', what do I check?
Most commonly: WORDPRESS_DB_HOST must be mariadb:3306 (the service name and its internal port), not localhost. Next, that the WordPress credentials match MariaDB's exactly (same user, same password, same database in the .env). And that MariaDB has finished initializing: with the healthcheck and depends_on: condition: service_healthy, WordPress waits until it is ready, which avoids the error on first startup.
Why are there two volumes?
Because there are two different things to persist. mariadb_data stores the database (posts, users, options). wordpress_data stores the WordPress files in /var/www/html, which include your uploads (wp-content/uploads) and any plugins or themes you install. If you were missing the WordPress volume, you would lose uploaded images even though the database stayed intact.
I changed the MariaDB password and it stopped connecting, why?
Same as with Postgres: MariaDB only creates the user and database the first time, with an empty volume. If it started before, mariadb_data keeps the original credentials and the new variables are ignored. For development, docker compose down -v removes the volumes and starts clean (it also deletes your WordPress content). To keep data, change the password via SQL and update the .env.
Do I need to publish the MariaDB port?
No. WordPress reaches MariaDB over the internal network (mariadb:3306), so the database publishes no port. Only WordPress publishes a port, bound to 127.0.0.1:8080 so the site is reachable from your browser without opening it to the network.
Can I use this in production?
Not as-is. It is a development and testing environment. A production WordPress needs HTTPS, backups of both the database and the uploads, strong passwords outside git, WordPress and plugin updates, and server hardening. The goal here is to get WordPress running locally in a couple of minutes, not a site exposed to the Internet.