RemarkableCloud

Apps / Docker Images

WordPress on Nginx.
A production image, free to pull.

RemarkableCloud packages WordPress on Nginx and PHP-FPM as a production-ready Docker image, so you get a familiar, fast web stack with caching and security defaults already configured.

docker pull ghcr.io/remarkablecloud/wordpress-nginx

Current build

7.1-r1

Updated Sep 10, 2026

Base

Nginx + PHP-FPM, digest-pinned

What's inside

fastcgi_cache page caching, brotli, Redis object cache, proxy-aware URLs

Upstream license

WordPress: GPLv2-or-later (Nginx: BSD-2-Clause)

Free to pull and run anywhere. Digest-pinned, updated deliberately; each build is recorded in the changelog.

The image

What our image adds.

RemarkableCloud packages WordPress on Nginx and PHP-FPM as a production-ready Docker image, so you get a familiar, fast web stack with caching and security defaults already configured.

Nginx and PHP-FPM 8.5 in one container

The base is the official `wordpress:php8.5-fpm` image (Debian 13, PHP 8.5.10), pinned by digest for reproducible builds. Nginx is added on top and both processes are supervised by supervisord, so the container is a complete web front end.

Page cache and object cache wired in

An Nginx fastcgi micro-cache serves anonymous pages without touching PHP, and its purge is handled by the Nginx Helper plugin. A Redis object cache speeds up logged-in and dynamic requests. Both caches are configured and the plugins are activated on first run.

Brotli and gzip compression

Brotli modules are installed alongside gzip for text, CSS, JavaScript, JSON, SVG, and woff2 assets, which reduces transfer size for modern browsers.

Proxy-aware canonical URLs

The image serves plain HTTP on port 80 and reads `X-Forwarded-Proto` and `X-Forwarded-Host` to build `WP_HOME` and `WP_SITEURL` per request. Nginx also trusts the private-range proxy for the real client IP, so logs and WordPress see the visitor address rather

Hardened defaults

Version banners are off, the in-dashboard file editor is disabled, and sensitive files and paths are blocked (details below).

Provisioning-gated healthcheck

The container reports healthy only after WordPress is installed and serving, so an orchestrator does not route traffic to a half-built site.

Rather skip the compose file? Deploy WordPress with one click on your own VPS: TLS, domain and database already wired.

Deploy WordPress in one click

The guide

Run it in production.

Docker Compose walkthrough

The recipe directory ships a docker-compose.yml that runs the full stack: the WordPress on Nginx container, a MariaDB database, and a Redis cache. A trimmed version looks like this:

name: wordpress-nginx

services:
  db:
    image: mariadb:11
    restart: unless-stopped
    environment:
      MARIADB_DATABASE: wordpress
      MARIADB_USER: wordpress
      MARIADB_PASSWORD: ${DB_PASSWORD:-change-me-db}
      MARIADB_ROOT_PASSWORD: ${DB_ROOT_PASSWORD:-change-me-root}
    volumes: [db-data:/var/lib/mysql]
    healthcheck:
      test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
      interval: 10s
      timeout: 5s
      retries: 12

  redis:
    image: redis:7-alpine
    restart: unless-stopped
    command: ["redis-server", "--requirepass", "${REDIS_PASSWORD:-change-me-redis}", "--save", "60", "1"]
    volumes: [redis-data:/data]

  wordpress:
    image: ghcr.io/remarkablecloud/wordpress-nginx:7.1-r1
    restart: unless-stopped
    depends_on:
      db:
        condition: service_healthy
    ports: ["${HTTP_PORT:-8081}:80"]
    environment:
      WORDPRESS_DB_HOST: db:3306
      WORDPRESS_DB_NAME: wordpress
      WORDPRESS_DB_USER: wordpress
      WORDPRESS_DB_PASSWORD: ${DB_PASSWORD:-change-me-db}
      REDIS_HOST: redis
      REDIS_PORT: "6379"
      REDIS_PASSWORD: ${REDIS_PASSWORD:-change-me-redis}
      WP_ADMIN_USER: ${WP_ADMIN_USER:-admin}
      WP_ADMIN_PASSWORD: ${WP_ADMIN_PASSWORD:-}
      WP_ADMIN_EMAIL: ${WP_ADMIN_EMAIL:-admin@example.com}
      WP_SITE_TITLE: ${WP_SITE_TITLE:-WordPress}
    volumes:
      - wp-content:/var/www/html/wp-content

volumes:
  db-data:
  redis-data:
  wp-content:

Start it with generated secrets:

REDIS_PASSWORD=$(openssl rand -hex 16) DB_PASSWORD=$(openssl rand -hex 16) \
WP_ADMIN_PASSWORD=$(openssl rand -hex 12) docker compose up -d

Then browse http://localhost:8081. The WordPress container stays unhealthy until provisioning is finished, which takes a little longer than the web server coming up, so wait for healthy before you send traffic. If you leave WP_ADMIN_PASSWORD unset, the provisioner generates one and prints it once to the container log.

In production you place a TLS-terminating reverse proxy (such as Traefik) in front of the WordPress service. The proxy handles certificates and forwards X-Forwarded-Proto and X-Forwarded-Host, which the image uses to set the canonical site URL.

Environment variable reference

The database and Redis variables are wired automatically by the RemarkableCloud App Platform from the managed MariaDB and Redis. They are listed here for standalone runs. The WP_* first-run variables are optional and used only when the site is first installed.

VariableRequiredDefaultDescription
WORDPRESS_DB_HOSTYes127.0.0.1Database host, as host or host:port; port defaults to 3306.
WORDPRESS_DB_NAMEYeswordpressDatabase name.
WORDPRESS_DB_USERYeswordpressDatabase user.
WORDPRESS_DB_PASSWORDYes(empty)Database password.
REDIS_HOSTNo(unset)Redis host; when set, the Redis object cache is enabled.
REDIS_PORTNo6379Redis port.
REDIS_PASSWORDNo(unset)Redis password; written as WP_REDIS_PASSWORD when present.
WP_ADMIN_USERNoadminAdmin username, created on first install only.
WP_ADMIN_PASSWORDNo(generated)Admin password; generated and printed to the log once if unset.
WP_ADMIN_EMAILNoadmin@example.comAdmin email, used on first install only.
WP_SITE_TITLENoWordPressSite title, used on first install only.

Ports and volumes:

  • Port: the container listens on 80 (plain HTTP). Map or proxy it as needed.
  • Volume: /var/www/html/wp-content holds uploads, plugins, and themes and should be persisted.
  • Health path: /rc-health.txt returns a static ok and is used by the container healthcheck.

Hardening notes

  • No version disclosure. Nginx sends no version banner (server_tokens off) and PHP has expose_php off. Error display is off while error logging stays on.
  • Sensitive files blocked. Direct requests to wp-config.php and xmlrpc.php are denied, dotfiles are denied except /.well-known, and PHP execution is denied inside uploads, files, and wp-content, so an uploaded script cannot be run.
  • Locked-down WordPress. The dashboard file editor is disabled (DISALLOW_FILE_EDIT), core auto-updates are limited to minor releases, and WP_ENVIRONMENT_TYPE is set to production.
  • Correct client IP. Nginx trusts the private-range proxy for X-Forwarded-For, so rate limits and logs use the real visitor address.
  • Non-root workers. Nginx and php-fpm workers run as www-data rather than root.

Backups

Two things need backing up: the database and the wp-content volume.

# Database dump
docker compose exec db \
  sh -c 'mariadb-dump -u root -p"$MARIADB_ROOT_PASSWORD" wordpress' > wordpress-db.sql

# wp-content archive
docker run --rm -v wordpress-nginx_wp-content:/data -v "$PWD":/backup alpine \
  tar czf /backup/wp-content.tgz -C /data .

Restore by importing the SQL dump into the db service and extracting the archive back into the wp-content volume. Take backups on a schedule and keep copies off the host.

Upgrades

  • WordPress core, themes, and plugins. Update these from the WordPress dashboard or with wp-cli, for example docker compose exec wordpress wp core update --allow-root. These changes live in the database and the wp-content volume, so they persist across container restarts.
  • The image itself. Pull a newer tag and recreate the container: docker compose pull wordpress && docker compose up -d wordpress. Because core and the baked plugins live in the image while your content lives in the volume, replacing the image updates the web stack and the baseline plugins without touching your uploads and installed extensions. Review the CHANGELOG.md before upgrading, and take a backup first.
  • Pin by digest in regulated environments so a rebuild of the same tag cannot change what you run.

Updates

Recent builds.

7.1-r1

Sep 10, 2026

Initial promoted build of WordPress on Nginx and PHP-FPM.

Or skip the ops

Host WordPress on your own private VPS.

One click installs this exact image on a VPS that is yours alone, domain and TLS wizard included. The same server runs as many apps as fit, with no per-app charge.

FAQ

Questions we get.

Does this include a database or Redis?

No. The image is the web server, PHP, and WordPress only. The database and Redis run as separate services, as shown in the Compose file, which keeps each component independently upgradable.

How does the page cache work?

Nginx keeps a fastcgi micro-cache of rendered pages and serves anonymous visitors from it without invoking PHP. The cache is bypassed for POST requests, query strings, admin and dynamic paths, and logged-in cookies, and the Nginx Helper plugin purges it when content changes.

Do I need to configure TLS inside the container?

No. The image serves plain HTTP on port 80 and expects a reverse proxy to terminate TLS. It reads the forwarded headers to set the correct HTTPS canonical URL.

Why is the container unhealthy for the first minute or two?

The healthcheck is intentionally gated on a provisioning marker. It reports healthy only after WordPress is installed and serving, so traffic is never routed to a half-built site.

Can I run more than one site from this image?

Run one WordPress site per container. To host several sites, run several stacks, each with its own database, Redis, and content volume.

Your server runs. You sleep.

Fully managed hosting from people who have been doing this since 2001.