One-Pyeong Server — Running WordPress with Docker
Self-hosting deployment part 2. Infrastructure skeleton in 30 minutes.
TL;DR
- Launch WordPress + MariaDB + Redis three containers with a single
docker-compose.ymlfile. - Only expose host port
127.0.0.1:8090. External routing handled by Cloudflare Tunnel (part 9). - Use Redis as an Object Cache for database query caching. Free and improves page response time by 30%.
- Five traps: Korean file name SCP, healthcheck
--connect, persisted password in named volume,WP_REDIS_HOST=redis,WORDPRESS_CONFIG_EXTRAX-Forwarded-Proto.
1. Why Docker — Host Installation vs Container
| Comparison | Direct Host (apt install) | Docker Compose ⭐ |
|---|---|---|
| Installation Simplicity | apt install apache2 php mariadb-server, etc. |
docker compose up -d |
| Dependency Conflicts | Conflicts like PHP 8.1 vs 8.3 arise | Container Isolation |
| Mini-PC OS Reinstallation | Start from scratch | Just compose.yml + data backup |
| Updates | System package manager (complexity) | docker compose pull && up -d |
| Coexistence of Different Containers (WP, Umami, cloudflared) | Difficult | Natural |
If the mini-PC is already running other services with Docker, the answer is Docker.
2. Structure on a Page
The three containers communicate within the same Docker network using hostnames (db, redis). Only the host 127.0.0.1:8090 is exposed externally.
3. Key docker-compose.yml
services:
wordpress:
image: wordpress:php8.3-apache
restart: unless-stopped
depends_on:
db:
condition: service_healthy
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_DB_USER: wp
WORDPRESS_DB_PASSWORD: ${DB_PASSWORD}
WORDPRESS_DB_NAME: wp
WORDPRESS_CONFIG_EXTRA: |
if (!empty($$_SERVER['HTTP_X_FORWARDED_PROTO']) && $$_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') {
$$_SERVER['HTTPS'] = 'on';
}
volumes:
- wordpress_data:/var/www/html
ports:
- "127.0.0.1:8090:80"
db:
image: mariadb:11
restart: unless-stopped
environment:
MARIADB_DATABASE: wp
MARIADB_USER: wp
MARIADB_PASSWORD: ${DB_PASSWORD}
MARIADB_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
volumes:
- wordpress_db_data:/var/lib/mysql
healthcheck:
test: ["CMD", "healthcheck.sh", "--connect"]
interval: 10s
timeout: 5s
retries: 6
start_period: 30s
redis:
image: redis:7-alpine
restart: unless-stopped
volumes:
- wordpress_redis_data:/data
volumes:
wordpress_data:
wordpress_db_data:
wordpress_redis_data:
Separate .env:
DB_PASSWORD=<openssl rand -hex 24>
DB_ROOT_PASSWORD=<openssl rand -hex 24>
chmod 600 .env. Do not include in git.
4. Start + Verification
cd /home/user/wordpress
docker compose up -d
docker compose ps
curl -I http://localhost:8090
If the response is HTTP/1.1 302 (redirecting to WordPress installation page), it is successful.
Access http://localhost:8090 in a browser → 5-minute wizard → create admin account (do not use admin as the name, choose your own to avoid initial brute force).
5. Activate Redis Object Cache
After installing the redis-cache plugin:
wp config set WP_REDIS_HOST redis --allow-root
wp config set WP_REDIS_PORT 6379 --allow-root
wp config set WP_REDIS_PREFIX byeolgorae: --allow-root
wp config set WP_CACHE true --raw --allow-root
wp redis enable --allow-root
WP_REDIS_HOST=redis is key. It is the Docker hostname. Using 127.0.0.1 results in failure as it only looks within its own container.
6. Traps — Real Experiences Faced
Trap 1. Korean and Box Characters in File SCP
While writing docker-compose.yml with minipc_write_file, the SSH escape broke due to a combination of Korean comments and special characters (bash: -c: unexpected end of file while looking for matching quotation mark).
→ Write plain text in c:\tmp\ on Windows → scp → mini-PC /tmp/ → circumvent by sudo cp.
Trap 2. mariadb healthcheck unsupported --innodb-initialized
Following the web guide with ["CMD", "healthcheck.sh", "--connect", "--innodb-initialized"] led to an unknown option error.
→ Use only --connect + extend start_period: 30s.
Trap 3. “Database Error” after changing .env password
After changing the DB password and running docker compose up -d, a 500 error appeared. Reason: The old password was retained in the MariaDB DB file stored in the named volume. The new password failed authentication.
→ Run docker compose down -v (the -v removes volumes) → restart with up -d. Do not do this if operational data exists; only at initial setup stage.
Trap 4. Redis Connection refused 127.0.0.1:6379
After setting WP_REDIS_HOST=127.0.0.1, it resulted in the container trying to access itself. Redis is in another container.
→ Set WP_REDIS_HOST=redis (Docker network hostname).
Trap 5. HTTPS Reverse Proxy Infinite Redirect
With Cloudflare Tunnel in front, it forwards HTTP to the mini-PC after terminating HTTPS. WordPress identifies itself as HTTP → redirects saying it must go to “HTTPS” → infinite loop.
→ Handle processing for X-Forwarded-Proto in WORDPRESS_CONFIG_EXTRA (see above yml).
FAQ
Q. How much memory is used?
WP (php-fpm or apache) ~200MB, MariaDB ~150MB, Redis ~30MB. Total ~400MB. Plenty of room with 8GB RAM on the mini-PC.
Q. What about disk space?
Image combined ~1GB. DB size varies (100 articles + images = ~500MB).
Q. Can I use PHP 8.2 instead of 8.3?
WordPress 6.x supports both 8.2 and 8.3. 8.3 is faster (JIT stabilization). It is recommended to use the latest.
Q. What if I use mysql instead of mariadb?
mariadb is compatible with mysql + free licensing + no Oracle dependency. Both will work. mariadb is recommended for single-user operations.
Q. How to backup?
Details covered in part 4 (backup, cache, images). Daily cron at 03:00 runs mariadb-dump → gzip → stored in Obsidian Vault.
Next Part Preview
Part 3 — For AI to Read: robots.txt and llms.txt. Specify allowances for 14 types of AI crawlers instead of discrimination against bots. Content exposure strategy for the GEO era.
Summary in One Line
Launching three containers (WordPress, MariaDB, Redis) through a single docker-compose.yml file in 30 minutes. Only exposing host 127.0.0.1:8090; external routing delegated to Cloudflare Tunnel. Traps include Korean SCP, healthcheck issues, password persistence in volumes, Redis host misconfiguration, and X-Forwarded-Proto problems.

Leave a Reply