Self-host WordPress with Redis, MySQL, Docker Compose and Traefik
Deploy WordPress on a root domain with Docker Compose, Traefik HTTPS, MySQL persistence, Redis object cache support, and safe Linux permissions.
WordPress is still one of the easiest ways to build a public website that can later be monetized with Google AdSense. In this setup, WordPress runs on the root domain while the technical documentation remains on a subdomain.
The target result:
1
2
3
https://example.com -> WordPress
https://www.example.com -> WordPress
https://docs.example.com -> documentation site
This guide deploys WordPress with:
- Docker Compose for repeatable deployment;
- Traefik for HTTPS and routing;
- MySQL for the WordPress database;
- Redis for object caching;
- no exposed MySQL or Redis ports;
- a permission script for safer WordPress file ownership.
This post uses placeholders for secrets. Never publish real database passwords, root passwords, API tokens, or
.envfiles.
Architecture
1
2
3
4
5
6
7
8
9
10
11
12
Internet
│
▼
Cloudflare DNS
│
▼
Traefik :80/:443
│
▼
WordPress container
├── MySQL container private Docker network only
└── Redis container private Docker network only
Only Traefik is public. WordPress joins both the public proxy network and the private wordpress_internal network. MySQL and Redis stay private.
Folder layout
The service lives under its own folder:
1
2
3
4
5
6
/home/ubuntu/wordpress/
├── docker-compose.yml
├── .env
├── wordpress.sh
├── backups/
└── html/
The html/ folder is mounted into the WordPress container at:
1
/var/www/html
Environment file
Create /home/ubuntu/wordpress/.env:
MYSQL_DATABASE=wordpress
MYSQL_USER=wordpress
MYSQL_PASSWORD=<generate-a-long-password>
Generate a strong password:
1
openssl rand -base64 36
Keep .env private:
1
chmod 600 /home/ubuntu/wordpress/.env
Docker Compose file
Create /home/ubuntu/wordpress/docker-compose.yml:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
services:
wordpress:
container_name: wordpress
image: wordpress:latest
restart: always
depends_on:
- db
- redis
environment:
WORDPRESS_DB_HOST: db:3306
WORDPRESS_DB_USER: ${MYSQL_USER}
WORDPRESS_DB_PASSWORD: ${MYSQL_PASSWORD}
WORDPRESS_DB_NAME: ${MYSQL_DATABASE}
WORDPRESS_CONFIG_EXTRA: |
define('WP_REDIS_HOST', 'redis');
define('WP_REDIS_PORT', 6379);
define('WP_CACHE_KEY_SALT', 'example.com');
volumes:
- ./html:/var/www/html
networks:
- proxy
- wordpress_internal
labels:
- "traefik.enable=true"
- "traefik.docker.network=proxy"
- "traefik.http.routers.wordpress.entrypoints=http"
- "traefik.http.routers.wordpress.rule=Host(`example.com`) || Host(`www.example.com`)"
- "traefik.http.middlewares.wordpress-https-redirect.redirectscheme.scheme=https"
- "traefik.http.routers.wordpress.middlewares=wordpress-https-redirect"
- "traefik.http.routers.wordpress-secure.entrypoints=https"
- "traefik.http.routers.wordpress-secure.rule=Host(`example.com`) || Host(`www.example.com`)"
- "traefik.http.routers.wordpress-secure.tls=true"
- "traefik.http.routers.wordpress-secure.tls.certresolver=cloudflare"
- "traefik.http.routers.wordpress-secure.service=wordpress"
- "traefik.http.services.wordpress.loadbalancer.server.port=80"
redis:
container_name: wordpress_redis
image: redis:7-alpine
restart: always
command: redis-server --appendonly yes
volumes:
- redis:/data
networks:
- wordpress_internal
db:
container_name: wordpress_db
image: mysql:8.0
command: --default-authentication-plugin=mysql_native_password
restart: always
environment:
MYSQL_DATABASE: ${MYSQL_DATABASE}
MYSQL_USER: ${MYSQL_USER}
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
MYSQL_RANDOM_ROOT_PASSWORD: '1'
volumes:
- db:/var/lib/mysql
networks:
- wordpress_internal
volumes:
db:
redis:
networks:
proxy:
external: true
wordpress_internal:
internal: true
Why MySQL 8 instead of MySQL 5.7?
Some ARM64 servers cannot run mysql:5.7 because that image does not provide a compatible ARM64 manifest. On ARM64, use mysql:8.0 or MariaDB.
This guide uses:
1
image: mysql:8.0
WordPress permissions script
Create /home/ubuntu/wordpress/wordpress.sh:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/bin/bash
set -euo pipefail
WP_PATH="/home/ubuntu/wordpress/html"
if [ ! -d "$WP_PATH" ]; then
echo "WordPress path not found: $WP_PATH" >&2
exit 1
fi
# Change ownership to www-data user and group
sudo chown -R www-data:www-data "$WP_PATH"
# Change permissions for folders to 755
sudo find "$WP_PATH" -type d -exec chmod 755 {} \;
# Change permissions for .php files to 644, excluding wp-config.php
sudo find "$WP_PATH" -type f -name "*.php" ! -name "wp-config.php" -exec chmod 644 {} \;
# Change permissions for wp-config.php to 440
if [ -f "$WP_PATH/wp-config.php" ]; then
sudo chmod 440 "$WP_PATH/wp-config.php"
fi
Make it executable:
1
chmod +x /home/ubuntu/wordpress/wordpress.sh
Why the script uses sudo: after changing files to www-data:www-data, the normal ubuntu user may not be allowed to run chmod on those files.
Start WordPress
1
2
cd /home/ubuntu/wordpress
docker compose up -d
Wait for the containers to initialize, then apply permissions:
1
./wordpress.sh
Check status:
1
docker compose ps
Expected containers:
1
2
3
wordpress
wordpress_db
wordpress_redis
Verify Redis
Redis should not be exposed to the internet. Test it from the host through Docker:
1
docker exec wordpress_redis redis-cli ping
Expected:
1
PONG
Check that WordPress knows the Redis host and port:
1
docker exec wordpress php -r 'require "/var/www/html/wp-config.php"; echo WP_REDIS_HOST . PHP_EOL; echo WP_REDIS_PORT . PHP_EOL;'
Expected:
1
2
redis
6379
Enable Redis Object Cache in WordPress
After finishing the WordPress installer:
- Open the WordPress admin dashboard.
- Go to Plugins → Add New.
- Install Redis Object Cache.
- Activate it.
- Go to Settings → Redis.
- Click Enable Object Cache.
The plugin should detect:
1
2
Redis host: redis
Redis port: 6379
Verify public routing
Check the root domain:
1
curl -I https://example.com/
Check the www domain:
1
curl -I https://www.example.com/
Before WordPress is installed, both may redirect to:
1
/wp-admin/install.php
That is normal.
Traefik and DNS notes
The Traefik labels expose WordPress on:
1
2
example.com
www.example.com
If you use traefik-cloudflare-companion, restart it after adding the new labels so it can create the www record:
1
docker restart traefik-cloudflare-companion
The root domain usually already points to the server. The www record may need to be created.
Backups
Back up the service folder and database volume before major changes.
1
2
cd /home/ubuntu
tar -czf wordpress-backup-$(date +%Y%m%d).tar.gz wordpress/
For database-level backups:
1
2
3
4
docker exec wordpress_db mysqldump \
-u wordpress \
-p \
wordpress > wordpress-db-$(date +%Y%m%d).sql
Do not publish backups. They can contain secrets, user data, plugin data, and database credentials.
SEO checklist for the WordPress site
For Google AdSense and search traffic, configure WordPress carefully:
- Set a clear site title and tagline.
- Use clean permalinks: Settings → Permalinks → Post name.
- Create required pages:
- About
- Contact
- Privacy Policy
- Terms / Disclaimer
- Install an SEO plugin such as Rank Math or Yoast SEO.
- Submit the root domain to Google Search Console.
- Add original articles, not copied content.
- Keep the site fast and mobile-friendly.
- Avoid intrusive ads before the site has useful content.
Recommended homepage structure:
1
2
3
4
5
Hero section: what Novelox is
Featured tutorials
Homelab categories
Link to docs.example.com
About/contact/privacy links
Common problems
mysql:5.7 does not start on ARM64
Use MySQL 8:
1
image: mysql:8.0
Redis plugin cannot connect
Check the constants:
1
2
define('WP_REDIS_HOST', 'redis');
define('WP_REDIS_PORT', 6379);
Check the Redis container:
1
docker exec wordpress_redis redis-cli ping
Permission script says Operation not permitted
Use sudo for find ... chmod commands after chown.
WordPress redirects to installer
That is expected until the first install wizard is completed.
Final result
You now have a WordPress site on the main domain, backed by MySQL and Redis, routed through Traefik, and ready for SEO work and future AdSense approval.
Related documentation
These posts connect to this topic and help build the bigger homelab picture:
- Build a Jekyll documentation site for your homelab — covers the documentation side of a public web presence.
- Build a homelab auth gateway with Traefik and Authentik — explains the Traefik routing foundation reused by WordPress.
- Run Garage S3 object storage in a homelab — can support media/object-storage workflows around hosted sites.