How to Set Up WordPress with Docker Compose

Before you start: versions, package names and storage identifiers change over time. Check the commands against the official documentation for your setup before running them, especially anything that creates, deletes or overwrites data.

What you will have working by the end

By following this guide you will run a single‑host WordPress stack built with Docker Compose. The stack includes a WordPress container and a MariaDB container, each using a named Docker volume so that your site files and database survive container restarts, host reboots, and Docker daemon upgrades. You will also verify that healthchecks report healthy status and that the WordPress installer completes successfully. The whole process takes roughly 20‑30 minutes if you already have Docker installed.

More Tutorials & Self-Hosting guides on TechPulseMind →

Prerequisites

Before you begin, make sure your system meets the following requirements. I will note how to verify each item.

Item Minimum requirement How to verify
Hardware Any x86_64 or ARM host capable of running Docker (≈2 GB RAM, 10 GB free disk) Run lscpu, free -h, and df -h
OS Linux distribution with a supported kernel (e.g., Ubuntu 22.04 LTS, Debian 12, Rocky Linux 9). Docker also runs on macOS/Windows for testing, but the production steps assume Linux. Run cat /etc/os-release
Docker Engine Docker Engine ≥ 20.10 (the version that introduced --health-interval and improved Compose V2) Run docker version --format '{{.ServerVersion}}'
Docker Compose Docker Compose V2 (plugin) ≥ v2.20.0 Run docker compose version
User privileges Ability to run docker without sudo (user added to docker group) or willingness to prefix commands with sudo Run groups $USER and look for docker
Network No other service already listening on the host port you intend to expose (default 8080) Run sudo ss -tlnp | grep :8080
Text editor Any editor capable of saving plain‑text files (vim, nano, VS Code, etc.)

If any of the above items are missing, install them using your distribution’s official Docker installation guide before proceeding. Do not use third‑party scripts that modify system packages outside the official repositories.

Step‑by‑step procedure

All steps must be performed in the exact order shown. Where a command could be destructive, it is explicitly marked.

Step 1 – Create a project directory

We will create a dedicated folder for the compose file and any auxiliary files. This keeps the workspace tidy and makes it easy to remove everything later if needed.

Explanation: mkdir -p creates the directory and any missing parent directories without error if the folder already exists. cd changes the shell’s working directory to the newly created folder.

mkdir -p $HOME/wordpress-docker
cd $HOME/wordpress-docker

Verification: pwd should return the path you just created.

Step 2 – Write the docker‑compose.yml file

We will now create the Compose definition. Because official image tags change over time, we use placeholders that you will replace with the current stable tags after checking the official Docker Hub pages for WordPress and MariaDB.

Explanation: The file defines two services (db for MariaDB and wordpress for WordPress), named volumes for persistent storage, restart policies, environment variables for credentials, and simple healthchecks. The depends_on clause ensures WordPress starts only after MariaDB reports healthy.

Before you copy the content, open your editor and create a file named docker-compose.yml in the current directory.

version: "3.9"

services:
  db:
    image: mariadb:
    container_name: mariadb
    restart: unless-stopped
    environment:
      MYSQL_ROOT_PASSWORD: 
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wordpress
      MYSQL_PASSWORD: 
    volumes:
      - db_data:/var/lib/mysql
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 10s
      timeout: 5s
      retries: 5

  wordpress:
    depends_on:
      db:
        condition: service_healthy
    image: wordpress:
    container_name: wordpress
    restart: unless-stopped
    ports:
      - "8080:80"
    environment:
      WORDPRESS_DB_HOST: db:3306
      WORDPRESS_DB_NAME: wordpress
      WORDPRESS_DB_USER: wordpress
      WORDPRESS_DB_PASSWORD: 
      WORDPRESS_TABLE_PREFIX: wp_
    volumes:
      - wp_data:/var/www/html/wp-content
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost"]
      interval: 15s
      timeout: 5s
      retries: 5

volumes:
  db_data:
  wp_data:

Now replace the placeholders:

Verification: Run docker compose config. If the file is syntactically correct, Compose will print the parsed configuration without error.

Step 3 – (Optional) Generate strong passwords

If you prefer not to type passwords directly into the file, generate them now and store them safely (e.g., in a password manager). Using the same passwords for both the MariaDB root account and the WordPress database user keeps the configuration simple.

Explanation: openssl rand -base64 32 produces 32 random bytes and encodes them in base64, yielding a 44‑character string suitable for a password.

DB_ROOT_PASS=$(openssl rand -base64 32)
WP_DB_PASS=$(openssl rand -base64 32)

Verification: echo $DB_ROOT_PASS and echo $WP_DB_PASS each output a 44‑character string.

Now edit docker-compose.yml and replace with the value of $DB_ROOT_PASS and with the value of $WP_DB_PASS. If you decide to set the passwords manually, make sure they are identical in both places.

Step 4 – Pull the images (first run)

Pulling the images ahead of time lets you see any network or authentication issues before starting the containers.

Explanation: docker compose pull contacts the configured registry (Docker Hub by default) and downloads the images referenced in the compose file.

docker compose pull

Verification: The output should show each image being downloaded; there should be no “Error response from daemon” messages.

Step 5 – Start the stack

We launch the containers in detached mode so they run in the background.

Explanation: docker compose up -d creates the containers, starts them, and attaches the defined volumes. The -d flag runs them in the background.

docker compose up -d

Verification: docker compose ps should list both containers with a State of Up. You can also follow the logs to see when each service reports readiness:

docker compose logs -f wordpress

Look for a line similar to “WordPress is ready” or “MariaDB ready”.

Step 6 – Verify healthchecks

Healthchecks give you a quick way to confirm that the internal processes are responding as expected.

Explanation: docker inspect extracts low‑level information about a container. We format the output to show the State.Health object and pipe it through jq for pretty‑printing. If jq is not installed, you can omit the pipe and read the raw JSON.

docker inspect --format='{{json .State.Health}}' wordpress | jq .
docker inspect --format='{{json .State.Health}}' mariadb   | jq .

Verification: After a few seconds each JSON should contain "Status":"healthy". If you see "Status":"starting" for longer than 30 seconds, revisit the troubleshooting section.

Step 7 – Access the WordPress installer

Open a web browser on the host (or any machine that can reach the host) and navigate to:

http://:8080

Replace with the actual address of your Docker host. If you are working locally, http://localhost:8080 works.

Verification: The page should load the WordPress language selection screen without a connection error.

Step 8 – Complete the WordPress setup wizard

Follow the on‑screen prompts:

  1. Choose your language and click Continue.
  2. Enter a Site Title, an Admin username, a strong Admin password, and an Admin email address.
  3. Click Install WordPress.

Verification: After installation you are redirected to the login page (/wp-admin). Logging in with the admin credentials you just created should present the WordPress Dashboard.

Step 9 – Confirm persistence across restarts

We will stop the containers, remove them, and then bring them back up to verify that the named volumes preserve your data.

Explanation: docker compose down stops the containers, removes them, and deletes the custom network, but it leaves named volumes intact. The subsequent docker compose up -d recreates the containers and re‑attaches the existing volumes.

docker compose down
docker compose up -d

Wait for the healthchecks to become healthy again (repeat Step 6). Then reload the site in your browser.

Verification: You should land directly on the admin login screen, not the installer. All previously created posts, pages, themes, plugins, and users remain present.

Step 10 – (Optional) Configure log rotation

If you intend to run the stack for months, uncontrolled log growth can fill your disk. Docker’s local driver lets you limit the size and number of log files.

Explanation: Adding a logging section under each service tells Docker to use the local driver with the supplied options. The example below caps each log file at 10 MiB and keeps a maximum of three files.

Edit docker-compose.yml and insert the following block inside both the db and wordpress service definitions (at the same indentation level as environment):

    logging:
      driver: "local"
      options:
        max-size: "10m"
        max-file: "3"

After saving the file, restart the stack:

docker compose up -d --force-recreate

Verification: Run docker inspect wordpress | grep -A2 Logging and you should see the max-size and max-file options reflected.

Verification summary (milestones)

Use this checklist to confirm that each major stage succeeded.

Milestone Command(s) to run Expected result
Project directory exists ls $HOME/wordpress-docker Directory listed
Compose file valid docker compose config No error, services printed
Images pulled docker compose pull Download progress, no errors
Containers start docker compose up -ddocker compose ps Both containers show State Up
Healthchecks healthy docker inspect --format='{{json .State.Health}}' <container> | jq . "Status":"healthy"
Web UI reachable curl -I http://localhost:8080 HTTP 200, Content-Type: text/html
Wizard completes Browser → installer → login Dashboard accessible
Data persists after down/up docker compose downdocker compose up -d → browser Admin login screen, no reinstall prompt
Logs rotate (if configured) docker inspect wordpress | grep -A2 Logging Shows max-size and max-file options

Common failure modes & fixes

Below are the most frequent issues I have seen when running this stack, along with concrete steps to resolve them.

Symptom Likely cause Fix
Container exits immediately (docker compose ps shows Exit 1) Mismatched or missing environment variables (e.g., WordPress DB password does not equal MariaDB password) Double‑check that WORDPRESS_DB_PASSWORD matches MYSQL_PASSWORD and that WORDPRESS_DB_USER matches MYSQL_USER. Re‑create with docker compose up --force-recreate.
Healthcheck stays starting Healthcheck command cannot reach the service (wrong port or container not ready) For MariaDB, ensure you are using mysqladmin ping -h localhost (no port needed). For WordPress, test inside the container: docker exec wordpress curl -f http://localhost. Adjust interval or timeout if needed.
Database connection error in WordPress installer WordPress cannot reach MariaDB (network or auth) Confirm WORDPRESS_DB_HOST is set to db (the service name). Check the WordPress container logs for Error establishing a database connection. Verify that the depends_on condition is present.
Data loss after first restart Using a host bind‑mount instead of a named volume, or forgetting to declare the volume under the top‑level volumes: key Replace any bind‑mount (e.g., ./wp-content:/var/www/html/wp-content) with a named volume as shown. Named volumes survive docker compose down.
Port already in use (Bind for 0.0.0.0:8080 failed: port is already allocated) Another service (e.g., Apache, nginx) already listening on the chosen host port Change the host side of the port mapping (e.g., "8081:80") or stop the conflicting service.
Permission denied on volume files (WordPress cannot write uploads) The container runs as www-data (UID 33) but a mistaken bind‑mount is owned by root Switch to a named volume (Docker manages ownership) or, if you must use a bind‑mount, run chown -R 33:33 /path/to/host/directory before starting the container.
Docker daemon out of disk space Volumes grow unchecked (log files, uploads) Implement log rotation (see Step 10) and periodically run docker system prune -af --volumes to reclaim space.
docker compose command not found Using the old standalone docker-compose binary instead of the V2 plugin Install the Compose plugin via your package manager (e.g., apt install docker-compose-plugin) or use docker compose (note the space). Verify with docker compose version.

Next steps (optional enhancements)

Once the basic stack is stable, you may want to add features that improve security, performance, or operational ease.

  • Reverse proxy with automatic TLS – Deploy Traefik, Caddy, or Nginx Proxy Manager in front of WordPress to obtain Let’s Encrypt certificates and host multiple sites on the same IP.
  • Automated backups – Run a side‑car container (e.g., restic or duplicity) that mounts wp_data and db_data and pushes snapshots to an off‑site target such as an S3‑compatible service. Schedule with host‑based cron or a dedicated cron container.
  • Watchtower for hands‑free updates – Run containrrr/watchtower with labels to automatically pull newer WordPress/MariaDB images and restart services only when the image digest changes. Test in a staging environment first.
  • Redis object cache – Add a redis:alpine service and install the Redis Object Cache plugin inside WordPress for faster page loads. Remember to add a healthcheck for Redis (redis-cli ping).
  • Monitoring and alerting – Export container metrics via cAdvisor or the Prometheus node exporter, visualize with Grafana, and set alerts on unhealthy healthchecks, high disk usage, or frequent restarts.
  • Version‑controlled configuration – Keep the docker-compose.yml (and an ignored .env file for secrets) in a Git repository. This enables peer review, easy replication to other hosts, and integration with CI pipelines.

Wrap up

You now have a production‑ready WordPress installation running inside Docker Compose, with persistent storage, healthchecking, and a clear path for future enhancements. Remember to consult the official Docker Hub pages for the latest image tags and to refer to the Docker documentation whenever you need to adjust logging, networking, or security settings. Happy self‑hosting!

Some links on this page may be affiliate links. If you buy through them we may earn a commission at no extra cost to you. See our affiliate disclosure.

How to Set Up Claude Code: Install, Configure and…Read next