A fully containerized deployment of WordPress backed by PostgreSQL (not MySQL), served behind Nginx, with privileged access managed through JumpServer, an open-source Privileged Access Management (PAM) platform.
This project deploys a complete WordPress stack on Docker with two distinguishing characteristics:
- WordPress runs on PostgreSQL instead of MySQL, made possible by the PG4WP compatibility layer.
- All administrative access is brokered through JumpServer, a bastion host that holds credentials, records every session, and removes the need for direct SSH access to the application containers.
The stack is intended as a reference for system administrators and DevOps engineers who need to run WordPress on PostgreSQL while enforcing controlled, auditable access to infrastructure.
| Component | Role | Exposed Port |
|---|---|---|
| Nginx | Web server | 80 |
| PostgreSQL | WordPress database | 5432 (internal) |
| WordPress | Application (via PG4WP) | 8080 |
| JumpServer | PAM / bastion host | 8088, 2222 |
Request flow
- Administrators connect through JumpServer rather than SSHing directly into any server.
- JumpServer stores the target credentials and records each session for audit.
- WordPress serves requests and persists all data to PostgreSQL through PG4WP.
- Ubuntu 24.04 (tested on VMware)
- Minimum 4 GB RAM (JumpServer is resource-intensive)
- Minimum 20 GB disk (larger images require headroom)
sudoprivileges- Outbound internet access
Note: The JumpServer, PostgreSQL, and WordPress images are large. Provision sufficient disk space to avoid a
No space left on devicefailure during setup.
# Update the system and install dependencies
sudo apt update
sudo apt install -y ca-certificates curl gnupg
# Add Docker's official GPG key
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
# Add the Docker repository
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# Install the Docker Engine
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
# Run Docker without sudo
sudo usermod -aG docker $USER
newgrp docker
# Verify
docker run hello-worldExpected result: the message Hello from Docker! is printed.
# Create a shared Docker network so the containers can reach each other
docker network create wp-network
# Run Nginx
docker run -d \
--name nginx \
--network wp-network \
-p 80:80 \
nginxVerify: open http://<server-ip> and confirm the "Welcome to nginx!" page.
docker run -d \
--name postgres \
--network wp-network \
-e POSTGRES_DB=wordpress \
-e POSTGRES_USER=wpuser \
-e POSTGRES_PASSWORD=wppassword \
postgresVerify: docker ps shows the postgres container in the Up state.
The official WordPress image targets MySQL by default. A custom image is required to bundle the PG4WP compatibility layer so WordPress can communicate with PostgreSQL.
mkdir ~/wordpress-pg && cd ~/wordpress-pg
cat > Dockerfile << 'EOF'
FROM wordpress:latest
RUN apt-get update && apt-get install -y \
libpq-dev \
unzip \
git \
&& docker-php-ext-install pdo pdo_pgsql pgsql
RUN git clone --branch v3.4.1 https://github.com/PostgreSQL-For-Wordpress/postgresql-for-wordpress.git /tmp/pg4wp-repo \
&& cp -r /tmp/pg4wp-repo/pg4wp /var/www/html/wp-content/pg4wp \
&& cp /var/www/html/wp-content/pg4wp/db.php /var/www/html/wp-content/db.php \
&& rm -rf /tmp/pg4wp-repo
EOFAbout PG4WP: the plugin intercepts the MySQL driver calls that WordPress issues and rewrites them into PostgreSQL queries. This lets WordPress run on PostgreSQL without MySQL present anywhere in the system. This build pins version v3.4.1, the latest release.
# Build the image
docker build -t wordpress-pg .
# Run WordPress
docker run -d \
--name wordpress \
--network wp-network \
-e WORDPRESS_DB_HOST=postgres \
-e WORDPRESS_DB_USER=wpuser \
-e WORDPRESS_DB_PASSWORD=wppassword \
-e WORDPRESS_DB_NAME=wordpress \
-e WORDPRESS_DB_TYPE=pgsql \
-p 8080:80 \
wordpress-pgVerify: open http://<server-ip>:8080 and confirm the WordPress installer (language selection) appears.
JumpServer requires its own PostgreSQL and Redis instances, so the full set is managed with Docker Compose.
mkdir ~/jumpserver && cd ~/jumpserver
cat > docker-compose.yml << 'EOF'
services:
db:
image: postgres:16
container_name: jumpserver_db
restart: always
environment:
- POSTGRES_DB=jumpserver
- POSTGRES_USER=jumpserver
- POSTGRES_PASSWORD=jumpserver
volumes:
- db_data:/var/lib/postgresql/data
redis:
image: redis:7
container_name: jumpserver_redis
restart: always
command: redis-server --requirepass jumpserver
jumpserver:
image: jumpserver/jms_all:latest
container_name: jumpserver
restart: always
privileged: true
environment:
- SECRET_KEY=jumpserversecretkey1234567890abcdef
- BOOTSTRAP_TOKEN=bootstraptoken1234
- LOG_LEVEL=ERROR
- DB_ENGINE=postgresql
- DB_HOST=db
- DB_PORT=5432
- DB_USER=jumpserver
- DB_PASSWORD=jumpserver
- DB_NAME=jumpserver
- REDIS_HOST=redis
- REDIS_PORT=6379
- REDIS_PASSWORD=jumpserver
- DOMAINS=<server-ip>:8088
ports:
- "8088:80"
- "2222:2222"
depends_on:
- db
- redis
volumes:
db_data:
EOFReplace
<server-ip>inDOMAINSwith the host's real IP, for example192.168.91.146:8088.
docker compose up -d
# Inspect the logs (allow 2-3 minutes for initialization)
docker logs jumpserver --tail 20Verify: open http://<server-ip>:8088 and sign in with admin / admin.
Important: Use PostgreSQL 16, not 13. Recent JumpServer releases issue the
REFRESH COLLATION VERSIONstatement, which is only supported on PostgreSQL 14 and later.
# Install and configure SSH and Python (required by JumpServer's Ansible checks)
docker exec -it wordpress bash -c "\
apt-get update && \
apt-get install -y openssh-server python3 && \
ln -sf /usr/bin/python3 /usr/bin/python && \
mkdir -p /run/sshd && \
echo 'root:p@ssword' | chpasswd && \
sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config && \
/usr/sbin/sshd"
# Attach JumpServer to the same network as WordPress
docker network connect wp-network jumpserver
# Find the WordPress container's IP
docker inspect wordpress | grep IPAddress- Assets → Asset list → Create → Linux
- Name:
WordPress - IP/Host:
172.18.0.4(the IP from the previous step) - Platform:
Linux, Port:22 - Nodes:
DEFAULT
- Name:
- Add an account: username
root, passwordp@ssword, marked as privileged. - Permissions → Asset Permissions → Create
- Users:
admin - Assets:
WordPress - Accounts:
All
- Users:
- Test: select the asset and run Test connectivity. A successful run reports
ok=1.
Open http://<server-ip>:8088/luna/, select the WordPress asset, and the terminal session opens.
Confirm: run ls /var/www/html and verify the WordPress files (wp-config.php, wp-content, and so on) are listed.
Issues encountered during setup, with their causes and resolutions.
| # | Symptom | Cause | Resolution |
|---|---|---|---|
| 1 | REMOTE HOST IDENTIFICATION HAS CHANGED |
Stale SSH host key | ssh-keygen -R <ip>, then reconnect |
| 2 | su: Authentication failure |
Ubuntu disables direct root login | Use sudo su or sudo -i |
| 3 | unzip: not found during build |
Base image lacks unzip | Add unzip to the Dockerfile |
| 4 | End-of-central-directory signature not found |
curl did not follow the GitHub redirect | Use git clone instead |
| 5 | WordPress targets MySQL, not PostgreSQL | Default image uses MySQL | Build a custom image with PG4WP |
| 6 | No space left on device |
Disk full | docker image prune -a, or extend the disk with lvextend and resize2fs |
| 7 | database "jumpserver" does not exist |
JumpServer has no database of its own | Add a dedicated PostgreSQL service to Compose |
| 8 | AUTH called without any password (Redis) |
Redis has no password set | Add --requirepass and REDIS_PASSWORD |
| 9 | syntax error at or near "REFRESH" |
PostgreSQL 13 is too old | Switch to postgres:16 |
| 10 | Configuration file has problems at login |
DOMAINS not configured |
Add DOMAINS=<ip>:8088 |
| 11 | Asset reported UNREACHABLE (SSH timeout) |
JumpServer and WordPress on different networks | docker network connect wp-network jumpserver |
| 12 | Invalid/incorrect password |
Container and JumpServer passwords differ | Align them with chpasswd |
| 13 | /usr/bin/python: not found (Ansible) |
WordPress container lacks Python | Install python3 and symlink it to python |
| 14 | Empty asset tree in Luna | No asset permission configured | Create an asset permission linking the user to the asset |
sudo lvextend -l +100%FREE /dev/mapper/ubuntu--vg-ubuntu--lv
sudo resize2fs /dev/mapper/ubuntu--vg-ubuntu--lv
df -h # confirm the new free space| Version | Date | Description | Author |
|---|---|---|---|
| 1.0 | 2026-06-22 | Initial release covering the full stack | - |
