The dependable way to run WordPress in Docker on a VPS is to deploy two Compose services: the official WordPress image for PHP and the web server, plus MySQL or MariaDB for the database. Connect them on Docker’s private network, persist both data directories in named volumes, and put an HTTPS reverse proxy in front. Docker starts the site; backups, patching, secrets, TLS renewal and recovery make it a production service.
Antihost offers managed WordPress hosting with free migration, one-click staging and a 14-day free trial.
This guide gives you a reproducible Compose deployment, the first-start procedure, reverse-proxy requirements, backup and restore commands, update choices, and checks for the failures that commonly appear after launch.
As an Amazon Associate I earn from qualifying purchases.
Do these 3 things before closing this tab:
1Clear out junk files and repair common Windows errors2Fix the driver behind crashes, sound loss and screen glitches3Repair Windows errors before they cause bigger problemsWhat the deployment looks like
A small production installation has four layers:
- VPS: A Linux server with Docker Engine, the Compose plugin, sufficient CPU, memory, storage and a firewall.
- Reverse proxy: NGINX, Apache or another proxy accepts public HTTP and HTTPS traffic, manages certificates and forwards requests internally.
- WordPress service: The official
wordpress:apacheimage supplies WordPress, PHP and Apache. - Database service: MySQL 8.0 or a compatible MariaDB release stores posts, users, settings and plugin data.
Compose creates a private network automatically. WordPress reaches the database by its service name, db; no database port needs to be exposed to the internet. The WordPress application tree is mounted at /var/www/html, while MySQL stores its files at /var/lib/mysql. Replacing a container then leaves the site data in place.
For a host-level proxy, bind the application only to a loopback address or run the proxy as another container on the same Compose network. In either case, expose public ports 80 and 443 only on the proxy, not on MySQL and not on an FPM socket.
#1 Best Overall
Requirements and sizing
WordPress.org’s 2026 requirements recommendation is PHP 8.3 or newer, MariaDB 10.11 or newer or MySQL 8.0 or newer, and HTTPS. Apache or NGINX is recommended as the robust, feature-rich server choice. The Docker image supplies PHP and Apache, but it does not supply the VPS’s CPU, RAM, disk, network, firewall or backup capacity.
| Item | What to provide | Why it matters |
|---|---|---|
| Operating system | Supported 64-bit Linux distribution with security updates | Docker Engine, the kernel and the proxy run on the host. |
| Container runtime | Docker Engine and the Docker Compose plugin | Compose defines, starts and updates the services. |
| PHP | 8.3 or newer through the selected WordPress image | Meets the current WordPress.org recommendation. |
| Database | MySQL 8.0+ or MariaDB 10.11+ | Stores all structured WordPress content. |
| Public networking | Static or stable VPS address, DNS A/AAAA records, ports 80 and 443 | Visitors and certificate authorities must reach the proxy. |
| Storage | Capacity for the OS, images, logs, uploads, database growth and backups | Media libraries and database tables usually grow faster than the image itself. |
| Backup destination | Separate, access-controlled storage with retention | A volume on the same VPS is not protection from disk or host loss. |
Size the VPS for concurrent traffic, image-processing plugins, scheduled jobs, media uploads and database I/O rather than for the compressed container size. Leave free disk space for an image pull and a database dump during an upgrade.
Quick wins for a faster PC:
Repair Windows errors before they cause bigger problemsFix Now →Scan for outdated or missing drivers - takes under a minuteDriver Scan →Clear out junk files and repair common Windows errorsFree Scan →Install Docker and create the project
- Install Docker Engine and the Compose plugin using the package instructions for your Linux distribution. Confirm both are available with
docker versionanddocker compose version. - Create a directory owned by your deployment account, for example
mkdir -p ~/wordpress-docker/secrets, and change into it. - Create a strong database password in
secrets/wp_db_password.txt. Put only the password on one line, restrict the file withchmod 600 secrets/wp_db_password.txt, and keep the directory out of version control. - Point the domain’s A and, if used, AAAA records at the VPS. DNS must resolve correctly before requesting a certificate.
- Allow SSH and the proxy’s public ports in the VPS firewall. Do not allow inbound MySQL, MariaDB or Docker API ports.
A secure Compose baseline
Save the following as compose.yml. It follows the official two-service pattern while using a Compose secret for the shared database password.
services:
wordpress:
image: wordpress:apache
restart: always
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_DB_USER: wpuser
WORDPRESS_DB_PASSWORD_FILE: /run/secrets/wp_db_password
WORDPRESS_DB_NAME: wordpress
secrets:
- wp_db_password
volumes:
- wordpress:/var/www/html
depends_on:
- db
db:
image: mysql:8.0
restart: always
environment:
MYSQL_DATABASE: wordpress
MYSQL_USER: wpuser
MYSQL_PASSWORD_FILE: /run/secrets/wp_db_password
MYSQL_RANDOM_ROOT_PASSWORD: "1"
secrets:
- wp_db_password
volumes:
- db:/var/lib/mysql
secrets:
wp_db_password:
file: ./secrets/wp_db_password.txt
volumes:
wordpress:
db:
What each part does
WORDPRESS_DB_HOST: dbuses Compose’s internal DNS. It is not a public hostname.wordpress:/var/www/htmlpreserves core files,wp-config.php, uploads, themes and plugins when the container is recreated.db:/var/lib/mysqlpreserves the database’s actual data directory.- The
_FILEvariables make the image read the password from a mounted secret instead of placing it directly in the Compose file. The official WordPress image supports this form. MYSQL_RANDOM_ROOT_PASSWORD: "1"asks the MySQL image to generate a root password. Routine WordPress connections use the limitedwpuseraccount.- No
portsentry is present. The database and application are therefore not published directly by this file.
A .env file is also suitable for non-secret deployment values, but do not commit passwords, private keys or certificate material. If your deployment system provides Docker secrets, use those rather than copying credentials into shell history.
Start WordPress for the first time
- From the project directory, pull the selected images:
docker compose pull. - Start in detached mode:
docker compose up -d. - Check service state with
docker compose ps. Both services should remain running. - Read recent output if either service is restarting:
docker compose logs --tail=100 wordpress db. - Allow initialization to finish. The first page request can take a couple of minutes while MySQL creates its data directory and WordPress prepares its files.
- Open the domain in a browser and complete the WordPress administrator setup. Choose the site language, administrator name, unique password and an address that can receive mail.
If the installer reports that it cannot connect to the database, verify that the service is named db, the password file contains no extra characters, and the database logs show that initialization has completed. Do not change the database volume casually: changing credentials after MySQL has initialized does not rewrite an existing data directory.
Add HTTPS with a reverse proxy
Terminate TLS at NGINX, Apache or another reverse proxy. The proxy should listen publicly on ports 80 and 443, redirect HTTP to HTTPS, renew certificates automatically and forward requests to the WordPress service over the private network.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Forward the original protocol
When TLS is terminated before the WordPress container, send the original scheme in the X-Forwarded-Proto header. A typical proxy block includes equivalent directives:
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
The official image specifically requires X-Forwarded-Proto to be set correctly in this arrangement. Without it, WordPress can think requests are HTTP, causing redirect loops, mixed-content warnings or incorrect secure-cookie behavior. With the documented environment configuration, no extra WordPress environment variable is normally required.
Choose where the proxy runs
- Proxy container: Add the proxy to the Compose project or an attached Docker network and route to
http://wordpress. Publish only the proxy’s 80/443 ports. - Proxy on the VPS host: Bind the WordPress HTTP port to loopback only, such as
127.0.0.1:8080:80, and configure the host proxy to use that address. It is not an internet-facing application port.
Keep certificate files readable only by the proxy, automate renewal, and test both the redirect and an HTTPS page after every proxy change. Never publish the database port for convenience.
Where WordPress and MySQL data live
List the named volumes with docker volume ls. The WordPress volume contains the application tree, generated configuration, uploaded media, themes and plugins. The database volume contains MySQL or MariaDB’s internal tables and transaction files. Docker may prefix the volume names with the Compose project name, so inspect the actual names before writing backup scripts.
Do not treat the containers as the data. docker compose down removes containers and the Compose network but preserves named volumes. docker compose down --volumes removes those volumes and therefore destroys the WordPress database and application files; reserve it for an intentional teardown after a verified backup.
Back up both layers
A database dump alone cannot restore uploaded images, themes or plugins. A file archive alone cannot restore posts, users, settings or orders stored in MySQL. A usable backup set contains both, is copied off the VPS, is encrypted and has a documented restore test.
Database dump
Run a consistent dump from the database container. The following reads the password from the mounted secret and writes the dump on the host; adjust the output directory to your protected backup location:
mkdir -p backups
docker compose exec -T db sh -c
'mysqldump --single-transaction -u"$MYSQL_USER" -p"$(cat /run/secrets/wp_db_password)" "$MYSQL_DATABASE"'
> backups/wordpress-$(date +%F).sql
Restrict the resulting file because it contains the site’s content and possibly personal data. Keep multiple dated copies rather than overwriting the only dump.
Archive the WordPress volume
First identify the real volume name with docker volume ls. Then archive it through a temporary container, replacing project_wordpress with the name shown on your host:
docker run --rm
-v project_wordpress:/source:ro
-v "$PWD/backups":/backup
alpine sh -c 'tar czf /backup/wordpress-files-$(date +%F).tar.gz -C /source .'
Prioritize wp-content/uploads, themes, plugins and the generated configuration when designing retention, but preserve the complete volume for a straightforward restore. Copy dumps and archives to storage outside the VPS and periodically perform a test restore on an isolated instance.
Restore order
- Stop the proxy and WordPress service so visitors cannot write new data during recovery.
- Restore the WordPress volume files with ownership and permissions suitable for the container’s web user.
- Import the SQL dump into the matching MySQL or MariaDB major version.
- Start the services, verify the site URL, media library, logins, permalinks and plugin behavior, then re-enable the proxy.
Record the restore date, image tags, database version and any manual changes. A backup that has never been restored is an assumption, not a recovery plan.
Update images and WordPress safely
The official image’s default behavior enables WordPress automatic updates in the persistent /var/www/html volume. That is convenient for a small site, but mutable files make it harder to reproduce an exact release. For controlled production deployments, build a derived image that pins the WordPress and PHP versions and includes your required themes, plugins and extensions. Deploy that image with writable mounts only where the application needs them.
A repeatable release procedure
- Record the current Compose file, image tag or digest, plugin list and database version.
- Take a fresh database dump and WordPress file backup.
- Pull or build the candidate image on a staging copy of the site.
- Exercise administrator login, front-end pages, media uploads, permalink rewrites, forms, email delivery, cron jobs and every business-critical plugin.
- Pull the tested image on production and recreate only the required service with
docker compose up -d. - Review
docker compose logs --tail=100 wordpress dband check the site from an external network. - Keep the previous image reference and backup until the new release has passed its observation period. Roll back the image and restore the database if the new code has changed data incompatibly.
Avoid a floating latest tag when change control matters. Rebuild and redeploy regularly so the base image receives current security fixes; pinning does not remove the need to update.
Plugins, themes and PHP extensions
Custom themes belong under /var/www/html/wp-content/themes/ and plugins under /var/www/html/wp-content/plugins/. Ensure the container’s web user can read them and can write only where WordPress actually needs to write, such as uploads or explicitly managed update paths. Excessive write access increases the impact of a compromised plugin.
Best Value
The official image cannot include every PHP extension that every plugin might request. If a plugin reports a missing extension, create a derived image with a Dockerfile beginning with FROM wordpress:apache, install the distribution package or compile the extension there, and rebuild the image. Do not install packages interactively inside a running container: those changes disappear on recreation and cannot be audited.
The Tool Desk
Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →When to use the FPM variant
The wordpress:<version>-fpm image has no public web server. It requires a reverse proxy that speaks FastCGI. FastCGI is inherently trusting, so never publish the FPM port with Docker’s public -p option. Keep it on a private Docker network shared only by the proxy, and expose the proxy’s HTTP/HTTPS ports instead. The Apache image is the simpler baseline when you do not have a specific reason to separate the web server.
Operational checks after launch
- Resolve the domain from an external network and confirm it reaches the intended VPS address.
- Confirm the firewall exposes only SSH and the proxy’s required public ports.
- Verify that the proxy reaches WordPress and that HTTP redirects to HTTPS without a loop.
- Check
docker volume lsafter deployment and after upgrades; both named volumes must exist. - Confirm that the database is reachable by
dbinside Compose and has no public listener. - Upload an image, load it from a fresh browser session and verify that it survives a container recreation.
- Test pretty permalinks, administrator login, password reset mail, scheduled events and any external API integrations.
- Review container logs after startup and after every image update. Set host-level log rotation so an error loop cannot consume the VPS disk.
- Keep a current database dump and file archive before changing WordPress, PHP, the proxy or a high-impact plugin.
Common failures and fixes
| Symptom | Likely cause | Action |
|---|---|---|
| “Error establishing a database connection” | Wrong service name, password mismatch or database still initializing | Check WORDPRESS_DB_HOST, the secret file and docker compose logs db. Do not delete the database volume. |
| Site redirects between HTTP and HTTPS | Proxy is not forwarding the original scheme | Set X-Forwarded-Proto to the incoming scheme and reload the proxy. |
| Uploads disappear after recreation | WordPress is writing to the container layer or the wrong path | Ensure the named volume is mounted at /var/www/html and verify the volume is the same one used by the service. |
| 502 or gateway timeout | Proxy target, network membership or service state is wrong | Check the target name and port from the proxy container, then inspect docker compose ps and WordPress logs. |
| Plugin reports a missing PHP module | Extension is absent from the base image | Build a derived image with the extension and deploy that image rather than modifying a live container. |
| WordPress cannot update files | Volume permissions or a deliberately read-only deployment | Give the web user write access only to required paths, or manage updates by building a new image. |
| Disk fills during an update | Insufficient headroom for old images, logs or backups | Remove unused images only after confirming rollback needs, rotate logs, move backups off-host and expand the VPS disk if growth is expected. |
Security responsibilities on a self-managed VPS
Docker isolates processes; it does not make an unattended VPS secure by itself. Patch the host operating system, Docker Engine, reverse proxy, PHP/WordPress image, themes and plugins. Use SSH keys and least-privilege accounts, restrict the firewall, protect secret and backup files, and monitor authentication and proxy logs. Keep the Docker socket inaccessible to untrusted processes because access to it is effectively host-level control.
Use HTTPS for every administrator and visitor connection, redirect plain HTTP, and renew certificates before expiry. Separate staging from production so plugin and image updates are tested against a copy of the database and uploads. If a plugin requires outbound access, allow only what the plugin needs and remove abandoned plugins instead of leaving disabled code installed.
Docker VPS versus managed WordPress hosting
Docker on a VPS is a control and reproducibility choice, not automatically a performance or cost win. Compare the operating models before committing:
| Decision axis | Self-managed Docker VPS | Managed WordPress service |
|---|---|---|
| Control | Full control of images, Compose, proxy, filesystem and network policy. | Provider controls much of the platform and may limit server-level changes. |
| Reproducibility | Pinned images and versioned Compose files can recreate an environment. | Provider-managed upgrades may be simpler but less configurable. |
| Security work | You patch the OS, Docker, proxy, PHP, WordPress and plugins. | The provider handles some infrastructure layers; plugin and content security remain your responsibility. |
| Recovery | You define backup scope, off-site retention, restore tests and recovery time. | Backup and restore features depend on the service’s documented policy. |
| Performance | You tune CPU, RAM, disk I/O, caching, CDN and database settings. | Capacity and caching are usually abstracted, with less low-level control. |
| Operations | You own monitoring, logs, staging, rollbacks and support. | Support and platform operations may be included, subject to the plan. |
Choose Docker on a VPS when you need server-level control, repeatable deployments or additional containers on the same host and can operate the stack. Choose managed hosting when minimizing system administration and recovery work is more important than controlling the runtime.
Quick Recap
Final deployment checklist
- WordPress uses PHP 8.3 or newer and MySQL 8.0 or MariaDB 10.11 or newer.
- WordPress and the database run as separate Compose services on a private network.
- Named volumes exist for both
/var/www/htmland/var/lib/mysql. - Only the reverse proxy publishes public HTTP/HTTPS ports.
- HTTPS redirects work, certificates renew, and
X-Forwarded-Protois correct. - Secrets are outside version control and backups are encrypted, off-host and tested.
- Image tags or digests are recorded, updates are staged, and a rollback image is retained.
- Logs, uploads, permalinks, email, cron and critical plugins have been verified.
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




