Post

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.

Self-host WordPress with Redis, MySQL, Docker Compose and Traefik

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 .env files.


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:

  1. Open the WordPress admin dashboard.
  2. Go to Plugins → Add New.
  3. Install Redis Object Cache.
  4. Activate it.
  5. Go to Settings → Redis.
  6. 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.

These posts connect to this topic and help build the bigger homelab picture:

This post is licensed under CC BY 4.0 by the author.