Running a WordPress Container on Synology
Synology's one-click WordPress package is fine for a single personal blog. I moved to Docker when I wanted a specific PHP version and a site I could lift onto another machine without a migration plugin. The setup is the official wordpress image plus a mariadb container, data under /volume1/docker, one compose file. This is on DSM 6.1 with the Docker package.
You need SSH enabled, the docker shared folder, and a free port for WordPress (I use 8081, since DSM's nginx owns 80 and 443). A hostname and reverse proxy rule for https://blog.example.com are optional.
Folders first
sudo mkdir -p /volume1/docker/wordpress/html
sudo mkdir -p /volume1/docker/wordpress/db
sudo chown -R 33:33 /volume1/docker/wordpress/html
sudo chown -R 999:999 /volume1/docker/wordpress/db
UID 33 is www-data inside the Debian-based wordpress image; 999 is mysql inside mariadb.
The compose file
Save this as /volume1/docker/wordpress/docker-compose.yml:
version: '2'
services:
db:
image: mariadb:10.2
container_name: wp-db
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: root-secret
MYSQL_DATABASE: wordpress
MYSQL_USER: wordpress
MYSQL_PASSWORD: wp-secret
volumes:
- /volume1/docker/wordpress/db:/var/lib/mysql
wordpress:
image: wordpress:4.8-apache
container_name: wp-web
restart: unless-stopped
depends_on:
- db
environment:
WORDPRESS_DB_HOST: db:3306
WORDPRESS_DB_NAME: wordpress
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD: wp-secret
volumes:
- /volume1/docker/wordpress/html:/var/www/html
ports:
- "8081:80"
The database is not published on any port; the two containers talk over the private network compose creates, and db resolves by service name.
Start it
cd /volume1/docker/wordpress
sudo docker-compose up -d
sudo docker-compose logs -f wordpress
On the first start the image copies WordPress into the empty html folder, which takes a minute. When the log shows apache2 -D FOREGROUND, open http://nas-ip:8081 and run the five-minute install. Both containers appear in the Docker GUI under Container, with the auto-restart flag already set from restart: unless-stopped.
Upload limit
The default PHP limit inside the image is 2 MB, which is useless for photos. It was the first thing I tripped over. Create a drop-in file in the bind-mounted folder; the image loads .htaccess from the document root, so this works without rebuilding:
# /volume1/docker/wordpress/html/.htaccess (append to the WordPress block)
php_value upload_max_filesize 64M
php_value post_max_size 64M
php_value memory_limit 256M
Alternatively add a php.ini snippet by mounting a file to /usr/local/etc/php/conf.d/uploads.ini in the compose file.
Behind the reverse proxy
Control Panel → Application Portal → Reverse Proxy → Create: Source HTTPS blog.example.com:443, Destination HTTP localhost:8081. Then, because WordPress builds absolute URLs, tell it the public address. Add to wp-config.php above the "That's all, stop editing" line:
define('WP_HOME', 'https://blog.example.com');
define('WP_SITEURL', 'https://blog.example.com');
if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') {
$_SERVER['HTTPS'] = 'on';
}
Without the HTTPS line WordPress sees plain HTTP from the proxy and falls into a redirect loop.
Dump the database nightly
Hyper Backup will pick up /volume1/docker/wordpress, but raw InnoDB files are only consistent if the container is stopped, and I don't trust a backup I can't restore from a plain SQL file. So I add a Task Scheduler entry (Control Panel → Task Scheduler → Create → Scheduled Task → User-defined script, user root, daily at 02:00):
mkdir -p /volume1/docker/wordpress/dump
docker exec wp-db sh -c 'exec mysqldump -uroot -p"$MYSQL_ROOT_PASSWORD" --single-transaction wordpress' \
| gzip > /volume1/docker/wordpress/dump/wordpress-$(date +%F).sql.gz
find /volume1/docker/wordpress/dump -name '*.sql.gz' -mtime +14 -delete
Did it work?
sudo docker-compose ps
curl -sI http://localhost:8081/ | head -n 1
curl -sI https://blog.example.com/wp-login.php | grep -i location
docker-compose ps should show both containers as Up. The local curl returns HTTP/1.1 302 Found (redirecting to the public URL) or 200 OK, and the HTTPS request must not redirect back to HTTP. Log in to /wp-admin, upload a 10 MB image, and confirm it lands under html/wp-content/uploads.
Things that bit me
- WordPress shows "Error establishing a database connection" right after
up -d: MariaDB is still initializing on first boot;depends_ononly waits for the container to start, not for the server to accept connections. Wait thirty seconds and reload. - The install page appears at
http://nas-ip:8081but images load fromhttp://nas-ip:8081after you moved it behind the proxy:WP_HOME/WP_SITEURLwere not set; the values in the database still point at the old address. Setting the constants overrides them. - "The uploaded file exceeds the upload_max_filesize directive in php.ini": the
.htaccesschange was not picked up becauseAllowOverridewas lost when you replaced the file wholesale. Check that the standard# BEGIN WordPressblock is still present. - Docker GUI complains "Docker API has failed" when you open the container details: it dislikes containers with
depends_onlinks created by compose. Use the CLI for those containers; they run fine.
Moving the blog to another NAS is now a copy of /volume1/docker/wordpress and a docker-compose up -d. That was the point of all this.