October DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsPC HealthRecommendedCrashes, freezes, slowdowns? Check your PC nowSpot repairable issues before they interrupt work.Check PCOctober DealsAmazon USDeal season is back - check today's better picksAmazon US: current deals, useful picks and tech finds.See Picks×
Skip to content
VGSources
Blog

How to Install WordPress with Docker Compose on a VPS (Ubuntu, HTTPS, and Backups)

Run WordPress and MariaDB on a VPS with Docker Compose. This guide covers Docker installation, secure networking, HTTPS, persistent volumes, backups, updates, and recovery.
Length12 min Posted Quest giverVGSources Team
Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Install WordPress on a VPS as two Docker Compose services: a WordPress container and a MariaDB database container, with named volumes mounted at /var/www/html and /var/lib/mysql. Keep the database private, bind WordPress to localhost, and let a reverse proxy provide the public HTTPS endpoint. This layout survives container replacement, supports repeatable updates, and gives you clear backup and rollback boundaries.

What you are building

The finished deployment has four layers:

  • Docker Engine and Compose: the runtime installed on the VPS.
  • WordPress: the web application and its themes, plugins, uploads, and configuration.
  • MariaDB: the database WordPress uses for posts, settings, users, and plugin data.
  • Reverse proxy and DNS: the public edge that accepts HTTP/HTTPS traffic, obtains a TLS certificate, and forwards requests to WordPress.

Compose gives the containers a private network. WordPress connects to the database at db:3306, where db is the Compose service name. You do not need to publish the database port on the VPS. Named volumes keep the database and WordPress tree when containers are recreated; deleting those volumes is a separate, destructive operation.

As an Amazon Associate I earn from qualifying purchases.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Prerequisites and sizing decisions

Supported VPS and operating-system checks

Use a supported 64-bit Linux VPS with administrative SSH access. Docker currently lists Ubuntu 22.04, 24.04, and 26.04 among its supported Ubuntu releases. Confirm the exact distribution and release before running installation commands:

cat /etc/os-release
uname -m

The VPS needs space for the operating system, Docker images, the database, WordPress media, backups, and container logs. There is no authoritative minimum disk size for every WordPress workload: a small text-only site and a media-heavy site have very different requirements. Check free space with df -h and plan a separate location for backups.

DNS and access

  • Create a DNS A record for the domain pointing to the VPS IPv4 address. Add an AAAA record only when the VPS has working IPv6 and the proxy is configured for it.
  • Keep an administrative SSH account available, preferably with key authentication.
  • Decide which reverse proxy will terminate TLS. Nginx, Caddy, Traefik, or another trusted proxy can forward the domain to the local WordPress listener.
  • Allow only the public services you actually operate. A typical web server exposes SSH, HTTP, and HTTPS; the database should remain private.

Install Docker Engine and the Compose plugin

Use Docker’s current repository instructions for the VPS distribution rather than installing the legacy standalone Compose binary. On Ubuntu, the documented package set includes Docker Engine, the Docker CLI, containerd, Buildx, and the Compose CLI plugin.

sudo apt update
sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo systemctl status docker
sudo docker run hello-world
sudo docker compose version

The final command should print the installed Compose plugin version. If Docker is not active, inspect sudo systemctl status docker and the service journal before continuing. Docker warns that published container ports can bypass rules written only for ufw or firewalld, so review the effective firewall policy after the stack is running rather than assuming a host rule protects every published port.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Create a private Compose project

Make the project directory

mkdir -p ~/wordpress
cd ~/wordpress

Keep compose.yaml, the environment file, and deployment notes in this directory. Store the directory in version control if appropriate, but never commit the file containing live passwords.

Create the environment file

Create .env with unique, long random values. The two passwords should be different: one is used by the WordPress database account and the other by the MariaDB root account.

MYSQL_DATABASE=wordpress
MYSQL_USER=wordpress
MYSQL_PASSWORD=replace-with-a-long-random-password
MYSQL_ROOT_PASSWORD=replace-with-a-different-long-random-password

Restrict the file to the deployment account:

chmod 600 .env

Do not put these credentials in a public repository, a shell history entry, or a support ticket. Compose reads the variables when it processes compose.yaml; changing them later does not automatically change an already-initialized database account.

Create compose.yaml

This Apache-based example binds WordPress to loopback on port 8080. The proxy on the VPS can reach it, while an external client cannot connect to that container port directly.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
services:
  db:
    image: mariadb:10.6
    restart: unless-stopped
    command: --default-authentication-plugin=mysql_native_password
    environment:
      MYSQL_DATABASE: ${MYSQL_DATABASE}
      MYSQL_USER: ${MYSQL_USER}
      MYSQL_PASSWORD: ${MYSQL_PASSWORD}
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
    volumes:
      - db_data:/var/lib/mysql

  wordpress:
    image: wordpress:apache
    restart: unless-stopped
    depends_on:
      - db
    ports:
      - "127.0.0.1:8080:80"
    environment:
      WORDPRESS_DB_HOST: db:3306
      WORDPRESS_DB_USER: ${MYSQL_USER}
      WORDPRESS_DB_PASSWORD: ${MYSQL_PASSWORD}
      WORDPRESS_DB_NAME: ${MYSQL_DATABASE}
    volumes:
      - wp_data:/var/www/html

volumes:
  db_data:
  wp_data:

The important boundaries are the service name, the private port binding, and the volume mounts. WordPress uses db:3306, not localhost and not a public VPS address. The database volume is mounted at /var/lib/mysql; the WordPress volume is mounted at /var/www/html.

The sample pins MariaDB to the 10.6 tag and uses the Apache WordPress image. Select an exact database tag after checking compatibility with your plugins and existing data, then record that choice in version control. The official Compose example also demonstrates a MySQL 8.0.27 option; neither database family is universally superior. Compatibility, lifecycle support, migration tooling, and your team’s operating experience should decide the choice.

Start the containers and complete WordPress setup

  1. Pull the images.
    docker compose pull
  2. Start the project in the background.
    docker compose up -d
  3. Confirm both services are running.
    docker compose ps
  4. Inspect the initial application output.
    docker compose logs --tail=100 wordpress

On the first launch, MariaDB initializes its data directory and WordPress prepares its files. The official image documentation cautions that initialization can take time; a blank or incomplete first response is not proof that the deployment failed. Wait briefly, then check both service logs.

For a temporary direct test, you can change the port mapping to 8080:80 and request http://VPS_IP:8080. This exposes the port on every interface, so use it only for a controlled test and remove the public binding afterward. The safer production mapping is the loopback binding shown above.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Once the proxy is forwarding the final domain, open the WordPress installer through that domain. Set the site address to the final https:// URL during setup. If WordPress is first configured with a localhost or temporary IP address, generated links and redirects can continue using that address until the site URL is corrected.

Put the site behind DNS, HTTPS, and a reverse proxy

Point the domain to the VPS

Publish the A record, wait for the record to resolve, and verify that it reaches the intended server. If you use IPv6, verify the AAAA path as well; an incorrect AAAA record can send some visitors to a different machine even when IPv4 is correct.

Forward the domain to WordPress

Configure the chosen reverse proxy to:

  • accept the domain on ports 80 and 443;
  • redirect plain HTTP to HTTPS;
  • proxy HTTPS requests to http://127.0.0.1:8080;
  • request and renew a certificate through the proxy’s supported ACME workflow;
  • pass the original host and scheme so WordPress can generate correct absolute URLs.

The proxy, not the database container, is the public edge. Keep the Compose port bound to loopback and do not add a database port mapping such as 3306:3306.

If you choose the FPM image

An FPM-based WordPress image is a different architecture from the Apache example. FPM speaks FastCGI, not HTTP, so it must sit behind a FastCGI-capable reverse proxy. Directly exposing FPM is inherently unsafe because the protocol is not intended to be a public listener.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The official image guidance also requires proxy configuration changes for FPM: remove the usual file-existence instruction and adjust the SCRIPT_FILENAME path from /var/www/html to /usr/src/wordpress. Do not copy an Apache proxy configuration unchanged to an FPM deployment.

Choice What the proxy must do Operational trade-off
wordpress:apache Forward ordinary HTTP to the container’s port 80 Simpler first deployment and fewer moving parts
FPM-based WordPress image Use FastCGI and the image’s required script path Can fit an existing FastCGI architecture, but misconfiguration or direct exposure is dangerous

Secure the VPS and the Compose project

  • Protect secrets: replace every sample password, keep .env mode 600, and limit shell and filesystem access to trusted administrators.
  • Keep the database private: WordPress reaches db:3306 on the Compose network; no host port is required.
  • Open only necessary firewall ports: normally SSH, HTTP, and HTTPS. Reconcile host firewall rules with Docker’s warning that published ports may bypass ufw or firewalld filtering.
  • Use the final HTTPS URL: this avoids mixed-content links, incorrect redirects, and cookies scoped to a temporary address.
  • Review image tags: floating tags make updates convenient but reduce reproducibility. Pin tags deliberately, record changes, and read release notes before deployment.
  • Control log growth: Docker’s default json-file logging can grow until it consumes the disk. Configure rotation or another logging driver before a busy site fills its volume.

Understand persistence before replacing anything

The named volumes are the persistence boundary:

Volume Container path What it contains
db_data /var/lib/mysql MariaDB’s database files
wp_data /var/www/html WordPress core files, uploads, themes, plugins, and generated files

Running docker compose down removes the containers and their network but preserves both volumes. Running docker compose down --volumes also deletes the volumes and therefore the database and WordPress tree. Treat the latter as destructive; do not use it as a routine way to restart the application.

Compose may prefix volume names with the project name. If you need to copy a volume, check the actual name first:

docker volume ls

Back up the database and WordPress files

A usable backup includes both the logical database and the WordPress files. Backing up only one side can leave you with posts but no uploads, or uploads without the database records that reference them. Store backup files outside the live volumes and outside the VPS when possible.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Database dump

Load the Compose variables into the current shell without printing them, then write a logical dump:

set -a
. ./.env
set +a
mkdir -p backups
docker compose exec -T db mariadb-dump -u root -p"$MYSQL_ROOT_PASSWORD" "$MYSQL_DATABASE" > backups/wordpress.sql

The dump command reads the root password from the environment variable in your shell. Protect the resulting SQL file just as carefully as the live database credentials.

Archive the WordPress volume

Stop writes briefly if you need a consistent file snapshot, then archive the volume to the project backup directory. Replace wordpress_wp_data with the actual name shown by docker volume ls if Compose used a different project prefix.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
docker run --rm 
  -v wordpress_wp_data:/data 
  -v "$PWD/backups":/backup 
  alpine tar czf /backup/wp_data.tgz -C /data .

Copy backups/wordpress.sql and backups/wp_data.tgz to storage that is independent of the VPS. A backup that remains on the same disk as the live site does not protect against disk loss or a compromised server.

Rehearse a restore

Do not trust a backup merely because the command completed. Restore it into an isolated Compose project or a disposable VPS:

  1. Create fresh volumes and start a database container with compatible credentials and the same database family.
  2. Import the SQL dump with the database client inside the database container.
  3. Extract the WordPress archive into the new WordPress volume while preserving the expected directory tree.
  4. Start WordPress and verify that the administrator login, posts, media, themes, plugins, and site URLs work.
  5. Record how long the restore took and which image tags and environment values were required.

Docker’s containerization guidance calls for backing up the WordPress files and MySQL database before moving an existing site. The same discipline applies before upgrades that could alter the database schema.

Update the stack without losing data

Updates are an operational task, not merely a new image download. Pin the image tags in your Compose file or deployment repository, review the release notes, and keep a tested rollback path and current database backup.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  1. Check the current state and recent errors:
    docker compose ps
    docker compose logs --tail=100 db wordpress
  2. Back up the database and WordPress volume.
  3. Change the image tag deliberately and commit the change if you keep the project under version control.
  4. Pull the selected images:
    docker compose pull
  5. Recreate the services:
    docker compose up -d
  6. Watch startup output and test the public HTTPS site, administrator login, media uploads, and critical plugins.

Recreating containers does not remove named volumes, so the data remains available to the replacement containers. Never combine a routine update with docker compose down --volumes. If a new image fails, restore the previous image tag and use the database backup if a schema change cannot be reversed safely.

Rebuild and redeploy regularly for current security updates, but schedule the work so you can observe logs and restore service if a plugin or extension is incompatible.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Troubleshoot common failures

WordPress reports that it cannot connect to the database

  • Check that WORDPRESS_DB_HOST is exactly db:3306; localhost points to the WordPress container itself, not the MariaDB service.
  • Compare MYSQL_DATABASE, MYSQL_USER, and MYSQL_PASSWORD in .env with the WordPress environment values.
  • Inspect both logs: docker compose logs db wordpress.
  • On a first start, wait for MariaDB initialization to finish before judging the result.
  • Remember that changing credentials in .env does not rewrite an already-initialized database. Change the database account deliberately or initialize a new test volume.

The first page is blank or incomplete

Wait for initialization and inspect the WordPress and database logs. Check that the reverse proxy is forwarding to 127.0.0.1:8080, that DNS resolves to this VPS, and that the proxy is not serving a stale certificate or upstream error page.

The domain redirects to localhost or an old IP

Complete the web installer through the final HTTPS domain, then check WordPress’s site URL settings. Also inspect proxy headers and any cached redirect at the browser or edge layer. A temporary direct test address should not become the canonical site URL.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

A plugin needs a PHP extension

The official WordPress image does not include every possible PHP library. Build a derived image that installs the required extension, pin the resulting base image and extension versions, and test the plugin before deploying it to the live site. Do not install ad-hoc packages inside a running container and expect them to survive recreation.

The VPS disk keeps filling

  • Check Docker images, containers, volumes, and logs with the Docker inspection commands appropriate to your distribution.
  • Configure rotation for the default json-file logs or select a logging driver with retention limits.
  • Move backups off the server and prune only unused resources after confirming that no rollback image or volume is needed.
  • Monitor the database and media volume separately; deleting a volume to reclaim space destroys the site data.

Apache versus FPM, and MariaDB versus MySQL

The Compose pattern is the same, but the operational decisions differ.

Decision Use the simpler path when Choose the other path when
Apache image versus FPM image You want an HTTP-speaking container and a straightforward proxy configuration. You already operate a FastCGI-capable edge, understand the required script path, and need that architecture’s tuning or caching model.
MariaDB versus MySQL Your existing plugins, exports, and operational procedures are tested with that database family. You have a compatibility or support requirement for the other family and have tested import, upgrades, and backups.

The official sample demonstrates both MariaDB and MySQL families but does not establish a universal winner. Validate the exact image tag, WordPress version, and plugin requirements together before production.

Production launch checklist

  • Docker Engine, CLI, Buildx, and the Compose plugin are installed from the distribution-appropriate Docker repository.
  • docker compose version, docker compose ps, and the service logs show a healthy startup.
  • The database has no published host port and is reachable from WordPress as db:3306.
  • db_data and wp_data are mounted at the intended paths.
  • The domain’s A and, if applicable, AAAA records point to the VPS.
  • The reverse proxy redirects HTTP to HTTPS and forwards to the loopback WordPress listener.
  • The WordPress site URL uses the final HTTPS domain.
  • Firewall rules allow only required services, with Docker’s published-port behavior accounted for.
  • .env is private, sample passwords are gone, and the file is excluded from source control.
  • Database and file backups exist off the VPS, and a restore rehearsal has succeeded.
  • Image tags, release notes, rollback steps, and a maintenance window are documented.
  • Container log rotation is configured so logs cannot consume the disk indefinitely.

How the lifecycle behaves

When you run docker compose up -d, Compose creates a private network, starts MariaDB and WordPress, and attaches the named volumes. When you replace a container during an update, the new container mounts the same volume and sees the existing database and WordPress files. When you run docker compose down, the containers and network disappear but the volumes remain. Only an explicit volume deletion, such as docker compose down --volumes, removes that persistent data.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

That separation is the reason this deployment is maintainable: container images can be replaced, while application state is backed up and retained independently. Treat the volumes, credentials, proxy configuration, and restore procedure as one production system rather than as disposable development files.

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.

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

More quests from Patch Notes

  1. How to Set Up a RedM (RDR2) Server in 2026: License Key, txAdmin and server.cfg, Step by StepBlog11min
  2. Best RedM (RDR2) Server Hosting in 2026: Comparing Four Hosts on Slots, Memory and PriceBlog10min
  3. How to Host a Mindustry Server in 2026: server-release.jar, Port 6567 and the Commands That MatterBlog7min
Recommended PC Tool
Recommended PC Tool
PC Slower Than It Used to Be?Free scan - under a minute
Outdated Drivers Are Slowing You DownFree scan - exact matches

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.