Skip to content
appsgit

Deploy guide

How to self-host WordPress with Docker Compose

WordPress Docker Compose setup with the official image and MySQL 8.4: secrets in .env, upload limits, HTTPS reverse proxy, database backups and safe upgrades.

  • Updated
  • Beginner
  • About 15 minutes

You will need

  • 1 vCPU / 1 GB RAM (2 GB for busy sites)
  • Docker + Docker Compose v2
  • A domain name pointing at the server
  • SSH access to the server

What is WordPress?

WordPress is the most widely used content management system on the web, powering blogs, business sites, shops (with WooCommerce) and membership sites. It is free and open source under the GPL, with tens of thousands of plugins and themes. The official wordpress Docker image bundles WordPress with PHP and Apache, so a full site is two containers: WordPress and a MySQL database.

Requirements

  • A Linux server with Docker Engine and Docker Compose v2. A small site runs on 1 vCPU and 1 GB of RAM; give it 2 GB if you use WooCommerce or page builders.
  • A domain with an A record pointing at the server.
  • Basic comfort with SSH.

Step 1: Prepare the server

This guide assumes Ubuntu 24.04 with Docker installed from the official Docker Engine guide.

mkdir -p ~/wordpress && cd ~/wordpress

Create uploads.ini to raise PHP's small default upload limit:

upload_max_filesize = 64M
post_max_size = 64M
memory_limit = 256M

Step 2: Create the Docker Compose file

This is based on the example in the official image's documentation, with secrets moved to .env, a pinned version and the database kept off the public network. Save it as docker-compose.yml:

services:
  wordpress:
    image: wordpress:7.1-apache
    restart: unless-stopped
    ports:
      - "127.0.0.1:8080:80"
    environment:
      WORDPRESS_DB_HOST: db
      WORDPRESS_DB_USER: wordpress
      WORDPRESS_DB_PASSWORD: ${DB_PASSWORD}
      WORDPRESS_DB_NAME: wordpress
    volumes:
      - wordpress:/var/www/html
      - ./uploads.ini:/usr/local/etc/php/conf.d/uploads.ini:ro
    depends_on:
      - db

  db:
    image: mysql:8.4
    restart: unless-stopped
    environment:
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wordpress
      MYSQL_PASSWORD: ${DB_PASSWORD}
      MYSQL_RANDOM_ROOT_PASSWORD: "1"
    volumes:
      - db:/var/lib/mysql

volumes:
  wordpress:
  db:

Create .env next to it:

DB_PASSWORD=CHANGE_ME

Replace CHANGE_ME with the output of openssl rand -hex 32. The database has no ports: entry, so it is reachable only from the WordPress container. The 7.1-apache tag receives WordPress 7.1 patch releases; WordPress core can also update itself from the dashboard, and those files persist in the wordpress volume.

Step 3: Start and open the app

docker compose up -d
docker compose logs -f wordpress

WordPress binds to localhost here, so set up HTTPS in Step 4 before running the installer. Running the installer on the final https:// domain means WordPress stores the right site URL from the start, and nobody else can reach the installer first and claim your site.

Step 4: Put it behind HTTPS

With Caddy on the host:

example.com, www.example.com {
    reverse_proxy 127.0.0.1:8080
}

Caddy gets Let's Encrypt certificates for both names. The official image's wp-config.php already honours the X-Forwarded-Proto header, so WordPress knows visitors are on HTTPS and you avoid redirect loops.

Now open https://example.com, choose a language, and fill in the site title, admin username, a strong password and your email. The localhost binding matters: Docker-published ports bypass ufw, so "8080:80" would leave the site reachable over plain HTTP on port 8080 even with the firewall on.

Backups and upgrades

Back up both the database and the files (themes, plugins, uploads):

docker compose exec -T db sh -c 'exec mysqldump -u wordpress -p"$MYSQL_PASSWORD" wordpress' > wp-db-$(date +%F).sql
docker run --rm -v wordpress_wordpress:/data -v "$PWD":/backup alpine \
  tar czf /backup/wp-files-$(date +%F).tgz -C /data .

Copy both files off the server. To move to a new WordPress image (for example a new PHP version), update the tag and run:

docker compose pull && docker compose up -d

Keep plugins and themes updated from the dashboard; outdated plugins are the most common way WordPress sites get hacked.

Troubleshooting

  • "Error establishing a database connection": the database is still initialising on first start, or DB_PASSWORD changed after the database was created. The password only applies on first initialisation.
  • Redirect loop after enabling HTTPS: the proxy is not sending X-Forwarded-Proto, or the site URL in Settings, General still uses http://.
  • "The uploaded file exceeds the upload_max_filesize directive": check that uploads.ini is mounted and restart the container.
  • Plugin installs ask for FTP credentials: file ownership in the volume is wrong. Run docker compose exec wordpress chown -R www-data:www-data /var/www/html.

Next steps

Install a caching plugin, enable automatic minor updates for plugins, add a security plugin with login rate limiting, set up outgoing email through an SMTP plugin, and schedule the backup commands above with cron.

Spotted something out of date? Tell us and we will update the guide.

FAQ

WordPress questions

Still curious? Email info@appsgit.com.

What port does WordPress use in Docker?

The official WordPress image serves Apache on port 80 inside the container. This guide publishes it on 127.0.0.1:8080 so only the reverse proxy on the same host can reach it, and visitors use ports 80 and 443 through the proxy.

What is the default WordPress admin login?

There is none. The first time you open the site, the installer asks for a site title, admin username, password and email. Pick an admin username other than admin and a long random password.

Is WordPress free?

Yes. WordPress is free and open source under the GPL. Self-hosting costs only the server and domain. Many themes and plugins are free too, with paid premium versions.

Should I use MySQL or MariaDB with WordPress?

Both work. The official Docker example uses MySQL; this guide uses the MySQL 8.4 long-term-support release. MariaDB is a drop-in alternative: swap the image for mariadb:11.8 and rename the MYSQL_ variables to MARIADB_.

How do I increase the WordPress upload size in Docker?

Mount a PHP ini file into /usr/local/etc/php/conf.d/ that raises upload_max_filesize and post_max_size, as shown in this guide, and make sure your reverse proxy allows request bodies at least as large.

WordPress.com vs self-hosted WordPress?

WordPress.com is a hosted service that limits plugins and themes on its cheaper plans. Self-hosted WordPress from wordpress.org gives you full control over plugins, themes, code and data, but you handle updates, security and backups yourself.