# 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.

## Key facts

| Fact | Value |
|---|---|
| App | WordPress (https://appsgit.com/apps/wordpress) |
| Difficulty | beginner |
| Time | about 15 minutes |
| Requirements | 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 |
| Last updated | 2026-10-06 |

## 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](https://docs.docker.com/engine/install/ubuntu/).

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

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

```ini
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`:

```yaml
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:

```bash
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

```bash
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](https://caddyserver.com/docs/) on the host:

```caddyfile
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):

```bash
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:

```bash
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.

## FAQ

### 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.

Prefer not to do it yourself? [appsgit installation help](https://appsgit.com/services/install) installs it on your server for a fixed quote.

---

Canonical page: https://appsgit.com/guides/wordpress
Source: appsgit (https://appsgit.com), the app store for github. Data from the GitHub API, refreshed nightly.
Machine access: JSON API https://appsgit.com/api/v1/apps (OpenAPI: https://appsgit.com/openapi.json), MCP server https://mcp.appsgit.com/mcp, full index https://appsgit.com/llms-full.txt.
