Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

This guide builds a WordPress site with Docker Compose using two containers: the official WordPress Apache image and MySQL 8.0. You will keep WordPress files and database data in named volumes, finish setup in a browser, and learn how to operate the stack safely. The example is ideal for local development and private staging; a public production site also needs HTTPS, backups, secrets, firewall rules, monitoring, and an update plan.

What you are building

Docker packages WordPress and its dependencies without requiring Apache, PHP, or MySQL to be installed directly on your computer. Docker Compose describes the services in one YAML file:

  • wordpress: WordPress running PHP and Apache.
  • db: MySQL 8.0.
  • Private network: the containers communicate internally.
  • Named volumes: persistent storage for WordPress files and MySQL data.

Compose is Docker’s tool for defining and running multi-container applications from a YAML configuration file (Docker documentation). Docker does not supply a domain, TLS certificate, backups, email delivery, monitoring, or disaster recovery automatically.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Prerequisites

  • Docker Desktop on macOS, Windows, or Linux, or Docker Engine with the Compose plugin on Linux.
  • A terminal and a browser.
  • An available host port, such as 8080.

Docker Desktop bundles Docker Engine, the CLI, and Compose. On a Linux machine that already has Docker Engine and Docker CLI, install the plugin with:

sudo apt-get update
sudo apt-get install docker-compose-plugin
docker compose version

Use the current docker compose command rather than the legacy docker-compose command. See Docker’s Compose installation guide for Desktop and RPM-based Linux instructions.

Verify the installation:

docker --version
docker compose version

Both commands should print version information. Docker Desktop’s free-use terms differ from Docker Engine’s licensing; check the current Desktop license terms if you are working for a larger commercial organization or government entity.

Create the project

mkdir wordpress-docker
cd wordpress-docker

Create a file named .env in this directory:

MYSQL_DATABASE=wordpress
MYSQL_USER=wordpress
MYSQL_PASSWORD=change-this-to-a-long-random-password
MYSQL_ROOT_PASSWORD=change-this-to-another-long-random-password

Use different, long passwords in a real deployment. Do not commit .env to a public Git repository. For production, prefer Docker secrets or a secrets manager. The official WordPress image supports _FILE environment-variable variants for several settings, allowing secrets to be read from files mounted under /run/secrets/ (image documentation).

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Create compose.yaml

Save this beginner-friendly configuration as compose.yaml:

services:
  wordpress:
    image: wordpress:apache
    restart: unless-stopped
    ports:
      - "8080:80"
    environment:
      WORDPRESS_DB_HOST: db:3306
      WORDPRESS_DB_USER: ${MYSQL_USER}
      WORDPRESS_DB_PASSWORD: ${MYSQL_PASSWORD}
      WORDPRESS_DB_NAME: ${MYSQL_DATABASE}
    volumes:
      - wordpress_data:/var/www/html
    depends_on:
      - db

  db:
    image: mysql:8.0
    restart: unless-stopped
    environment:
      MYSQL_DATABASE: ${MYSQL_DATABASE}
      MYSQL_USER: ${MYSQL_USER}
      MYSQL_PASSWORD: ${MYSQL_PASSWORD}
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
    volumes:
      - db_data:/var/lib/mysql

volumes:
  wordpress_data:
  db_data:

What the important lines mean

  • wordpress:apache includes a ready-to-use HTTP server, making it simpler than the FPM variant for beginners.
  • 8080:80 maps host port 8080 to Apache’s port 80 inside the container. Change only the left number if 8080 is busy.
  • WORDPRESS_DB_HOST: db:3306 uses the Compose service name. Inside the WordPress container, localhost means WordPress itself, not MySQL.
  • depends_on starts the database service first, but does not guarantee that MySQL is ready to accept connections.
  • wordpress_data:/var/www/html preserves core files, uploads, themes, plugins, and generated configuration.
  • db_data:/var/lib/mysql preserves the database.

The unversioned wordpress:apache tag is convenient for a tutorial but moves over time. For reproducible deployments, select a tested explicit tag from the official image page, such as a currently available PHP 8.3 Apache tag, and test upgrades before production use. Do not assume any example tag remains the latest.

Optional: wait for database readiness

For more reliable startup coordination, add a health check and change depends_on:

services:
  wordpress:
    depends_on:
      db:
        condition: service_healthy

  db:
    healthcheck:
      test: ["CMD-SHELL", "mysqladmin ping -h localhost -u root -p$${MYSQL_ROOT_PASSWORD}"]
      interval: 10s
      timeout: 5s
      retries: 10

Merge these keys into the main file rather than creating duplicate service definitions. A health check improves orchestration; it is not a backup or monitoring system.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Start WordPress

docker compose up -d

Docker downloads images, creates a private network and the two named volumes, and starts the containers. Check their state:

docker compose ps

Follow logs when the first startup takes a little while:

docker compose logs -f wordpress
docker compose logs -f db

When initialization completes, open http://localhost:8080. If Docker runs on another machine, use http://SERVER-IP:8080 after allowing that port through the server firewall.

Complete the browser setup

Choose a language, enter a site title, create an administrator account, and submit the form. Use a unique administrator username (not admin), a strong password, and an email address you can access. Never reuse the MySQL password as the WordPress administrator password.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

WordPress.org currently recommends PHP 8.3 or newer, MySQL 8.0 or newer (or MariaDB 10.11 or newer), and HTTPS for modern installations. Check its requirements page when choosing image and database versions.

Verify persistence

  1. Create a test post and upload an image.
  2. Stop and remove the containers, retaining volumes:
    docker compose down
  3. Start them again:
    docker compose up -d
  4. Open the site and confirm the post and image remain.

docker compose down normally removes containers and the network but keeps named volumes. This command is destructive:

docker compose down --volumes

It also deletes both volumes and permanently removes the stored site and database unless you have a backup. Use it only for a disposable test reset.

Everyday commands

# Start in the background
docker compose up -d

# Stop and remove containers, keep data
docker compose down

# Restart services
docker compose restart

# Show services
docker compose ps

# Follow all logs
docker compose logs -f

# Download newer image versions, then recreate services
docker compose pull
docker compose up -d

Change the host port

If you see a port-allocation error, change the mapping in compose.yaml:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
ports:
  - "8081:80"

Then run docker compose up -d and visit http://localhost:8081. The right-hand port remains 80 because that is Apache’s port inside the container.

Themes, plugins, and development files

The normal workflow is **Plugins > Add New Plugin** or **Appearance > Themes** in the dashboard. Custom files live under:

/var/www/html/wp-content/plugins/
/var/www/html/wp-content/themes/

A named volume is the easiest default. For active host-side development, you can use a bind mount:

volumes:
  - ./wordpress:/var/www/html

Bind mounts are convenient to edit but can cause ownership, permissions, SELinux, and filesystem-performance problems, especially on macOS and Windows. Do not solve every permission issue with chmod -R 777; correct ownership and security policy instead. The official image also documents mounting individual themes or plugins and building a custom image from /usr/src/wordpress.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Some plugins require PHP extensions or libraries absent from the base image. If installation or execution fails, check the plugin requirements and build a custom image rather than assuming every plugin works out of the box.

Common problems

“Error establishing a database connection”

docker compose ps
docker compose logs db
docker compose logs wordpress

Confirm that WORDPRESS_DB_HOST is db:3306, and that the database name, user, and password match the MYSQL_* values. MySQL initialization variables primarily apply when /var/lib/mysql is empty. Changing passwords in .env after the volume was initialized does not necessarily change existing credentials. Back up before resetting a volume.

“Bind for 0.0.0.0:8080 failed”

Another service owns the host port. Change 8080:80 to an unused port such as 8081:80.

Uploads or updates fail

docker compose exec wordpress ls -la /var/www/html/wp-content
docker compose logs wordpress

Check volume ownership, read/write permissions, available disk space, and host security controls. A read-only mount or incompatible bind-mount ownership can trigger FTP-password prompts.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The site cannot be reached

Check that Docker Desktop is running, the containers are up, and you are using the mapped port:

docker compose ps
docker compose logs -f wordpress
docker ps

For a remote server, verify its public IP, firewall, cloud security rules, and whether another service is using the port.

A container exits immediately

docker compose ps -a
docker compose logs wordpress
docker compose logs db

Look for invalid YAML, missing variables, database initialization errors, insufficient disk space, unsupported CPU architecture, or a corrupt/incompatible old volume. Check that the selected image tag supports your machine, particularly on ARM devices.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Back up before you need to

A WordPress backup must include both the MySQL database and the files in /var/www/html. The files contain uploads, themes, plugins, and configuration; a database dump alone cannot restore the complete site.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

A basic logical export is:

docker compose exec -T db 
  mysqldump -u root -p"$MYSQL_ROOT_PASSWORD" "$MYSQL_DATABASE" 
  > wordpress-backup.sql

Restore with:

cat wordpress-backup.sql | docker compose exec -T db 
  mysql -u root -p"$MYSQL_ROOT_PASSWORD" "$MYSQL_DATABASE"

For production, use a dedicated script or secret file so passwords do not appear in shell history, and separately copy or snapshot the WordPress volume. Test restores, retain multiple versions, and store at least one copy outside the host.

Preparing a public production deployment

The two-container example is not a complete hardened public service. Before launch:

  • Terminate HTTPS: Put a reverse proxy such as NGINX, Caddy, or Traefik in front of WordPress and configure certificates. When TLS is terminated upstream, pass the correct X-Forwarded-Proto header; the official image documents this requirement.
  • Keep MySQL private: Do not publish port 3306 unless there is a specific, controlled administrative need.
  • Protect secrets: Use Docker secrets or an external secret manager rather than committing passwords.
  • Use a firewall: Expose only required HTTP/HTTPS and administration ports.
  • Pin and test images: Use explicit tags or digests, stage upgrades, and keep a rollback path.
  • Monitor: Retain logs, watch disk space and resource usage, and alert on failed containers.
  • Plan updates: Back up before WordPress, plugin, theme, database, and image upgrades.
  • Configure email: Fresh containers may not deliver password-reset or contact-form mail. Use an SMTP plugin with an authenticated provider.

Docker’s production Compose guidance recommends production-specific configuration rather than treating a development file as finished infrastructure.

Apache, FPM, or a custom image?

Choice Best for Trade-off
wordpress:apache Beginners, local development, simple servers Less flexible than a separately managed proxy and FPM stack
wordpress:fpm Advanced deployments with NGINX or another reverse proxy Requires correct FastCGI configuration; do not publish it directly without understanding the security implications
Custom image Pinned WordPress, plugins, themes, and PHP extensions Requires Dockerfile maintenance and rebuilds

Use named volumes for simplicity, bind mounts when you need host editing, and custom images when the deployment artifact itself must be reproducible. MySQL is used here because it matches the official example; MariaDB 10.11 or newer is also supported by WordPress.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

When Docker is the wrong tool

Docker is a good fit when you want repeatable environments, several WordPress versions on one machine, portable Compose files, and control over networking and storage. Managed WordPress hosting may be better if your real goal is simply to publish a site with automatic backups, updates, staging, support, and security operations. Compare the operational responsibilities before choosing a VPS: a cloud server gives you control, but you remain responsible for the operating system, firewall, backups, HTTPS, monitoring, and WordPress maintenance. Useful starting points are the WordPress.com service and the WordPress.org hosting directory.

Reset a disposable test site

Only for a local installation whose data you do not need:

docker compose down --volumes
docker compose up -d

This creates a fresh database and WordPress volume. Never run it on a site you intend to keep without verified backups.

Frequently Asked Questions

Why is the database host `db` instead of `localhost`?

Compose provides service-name DNS on its private network. `db` identifies the MySQL service; `localhost` inside the WordPress container refers to the WordPress container itself.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Will `docker compose down` delete my WordPress site?

Not when the site uses the named volumes shown here. It removes containers and the network but normally keeps volumes. `docker compose down –volumes` also deletes the stored files and database.

Can I expose this setup directly to the Internet?

Only after adding production controls such as HTTPS through a reverse proxy, firewall rules, protected secrets, backups, updates, monitoring, and a recovery plan. Keep MySQL on the internal Compose network.

The Bottom Line

For a repeatable local WordPress installation, create the .env and compose.yaml files, run docker compose up -d, and finish setup at http://localhost:8080. The named volumes preserve your site across container recreation. Before making it public, add HTTPS, tested backups, secret management, pinned images, and ongoing maintenance.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.