Deploy WordPress with MySQL, Redis, Docker and Traefik
Run a production-style WordPress stack with persistent files, MySQL, Redis caching, and HTTPS routing.
Run a production-style WordPress stack with persistent files, MySQL, Redis caching, and HTTPS routing.
This is a standalone service guide. It explains the service in isolation so you can deploy it without copying unrelated parts of another stack.
All domains, emails, usernames, IP addresses, passwords, and tokens in this post are placeholders. Replace them for your own server, but do not publish real secrets or private infrastructure details.
What this service does
Route or access pattern:
1
example.com and www.example.com
Main components:
1
WordPress, MySQL database, Redis object cache, persistent WordPress files.
Network and port model:
1
Traefik routes HTTPS to WordPress port 80; MySQL and Redis stay internal.
In a Docker homelab, the safest pattern is to keep the application private on Docker networks and let the reverse proxy handle HTTPS traffic.
Folder layout
Use a dedicated folder:
1
2
3
4
5
/home/ubuntu/wordpress/
├── docker-compose.yml
├── .env
├── data/ # or service-specific persistent data
└── backups/ # optional local backup destination
Keep .env private. Public documentation should show only placeholders.
Environment file
Example .env values:
MYSQL_DATABASE=wordpress
MYSQL_USER=wordpress
MYSQL_PASSWORD=<database-password>
WORDPRESS_DOMAIN=example.com
Rules:
- generate long random passwords and tokens;
- keep
.envout of Git; - do not paste production values into public tutorials;
- rotate exposed credentials immediately if they ever leak.
Compose pattern
A minimal Traefik-aware Compose pattern looks like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
services:
wordpress:
image: <service-image>:<version>
container_name: wordpress
restart: unless-stopped
env_file:
- .env
networks:
- proxy
labels:
- "traefik.enable=true"
- "traefik.docker.network=proxy"
- "traefik.http.routers.wordpress.entrypoints=https"
- "traefik.http.routers.wordpress.rule=Host(`example.com`)"
- "traefik.http.routers.wordpress.tls=true"
- "traefik.http.routers.wordpress.tls.certresolver=cloudflare"
- "traefik.http.services.wordpress.loadbalancer.server.port=<internal-port>"
networks:
proxy:
external: true
Adapt image names, volumes, internal ports, and service-specific environment variables for the actual application.
Deployment steps
Create the directory:
1
2
mkdir -p /home/ubuntu/wordpress
cd /home/ubuntu/wordpress
Create the .env file with placeholder values replaced by your own secrets:
1
2
nano .env
chmod 600 .env
Create or edit docker-compose.yml, then start the service:
1
docker compose up -d
Check status:
1
2
docker compose ps
docker compose logs --tail=100
Verification checklist
Verify the container is running:
1
docker ps --filter name=wordpress
Verify Traefik can see the route:
1
docker logs traefik --tail=100
Verify the public route, if the service has one:
1
curl -I https://service.example.com
Expected results vary by service:
200means the app is reachable;302can be correct when authentication redirects to an identity provider;401can be correct for APIs that require authentication;404usually means the Traefik router rule did not match.
Backup checklist
Back up at least:
1
2
3
/home/ubuntu/wordpress/docker-compose.yml
/home/ubuntu/wordpress/.env # private backup only
/home/ubuntu/wordpress/data/ # or named Docker volumes
For database-backed services, prefer application-aware dumps in addition to copying files:
1
docker compose exec <database> <dump-command> > backup.sql
Never publish backup archives. They often contain tokens, password hashes, uploads, user data, or private keys.
Common problems
The domain returns 404
Check:
- the container has
traefik.enable=true; - the service is attached to the
proxynetwork; - the router
Host(...)rule matches the exact domain; traefik.docker.network=proxyis set when the container has multiple networks.
The domain returns bad gateway
Traefik found the route but cannot reach the internal app port.
Check the application logs and confirm the internal port used by:
1
traefik.http.services.<service>.loadbalancer.server.port
The service starts but data disappears after restart
The persistent directory or Docker volume is missing. Add a named volume or a bind mount for the service data before using it seriously.
Authentication loops or redirects fail
Check the public URL, trusted proxy headers, cookie domain, and whether the app knows it is behind HTTPS.
Security notes
- Keep admin dashboards behind authentication or private networks.
- Do not expose databases, caches, or admin APIs directly to the internet.
- Use least-privilege API tokens.
- Prefer internal Docker networks for dependencies.
- Keep public docs generic: use
example.com,<token>,<password>, and documentation IP ranges.
Service documentation map
This post is part of the standalone homelab service documentation series. Use these guides together when building the full stack:
- Traefik reverse proxy
- Authentik SSO
- Cloudflare Companion
- Headscale control server
- Pi-hole private DNS
- Garage S3 object storage
- WordPress stack
- WatchParty service
- Jekyll documentation site
- OpenClaw gateway
- Vaultwarden password manager
- Portainer Docker management
- Karakeep bookmarks
- Discourse forum
- PocketBase backend
- LM Studio local AI API