Install & Setup
Docker Compose
Run PE Community and its required services with persistent storage and a single HTTPS entry point.
Installation
Prepare the host before starting the deployment.
Environment variables
Configure domains, secrets, email, and optional security values.
Deployment
Review public routing and production operating boundaries.
Backup and restore
Protect PostgreSQL and uploaded files before changes.
Upgrades
Review release-specific migration and compatibility guidance.
Troubleshooting
Diagnose startup, routing, background work, and storage problems.
Introduction
Use Docker Compose to run PE Community, PostgreSQL, Redis, background processing, and the HTTPS entry point as one managed deployment. Named volumes preserve application data when containers are recreated.
Before you start
- Install Docker Engine and the Docker Compose plugin.
- Copy and configure `.env` as described in Environment variables.
- Create a DNS record for APP_DOMAIN when using automatic HTTPS.
- Allow public traffic to ports 80 and 443.
- Reserve enough disk space for PostgreSQL, uploaded files, Redis state, TLS data, and backups.
Production compose file
Use the repository `docker-compose.prod.yml` beside `.env`. It builds the API, worker, and web images from the checked-out source and starts the supporting services.
services:
postgres:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_USER: pe
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}
POSTGRES_DB: pe_community
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U pe -d pe_community"]
interval: 5s
timeout: 5s
retries: 20
redis:
image: redis:7-alpine
restart: unless-stopped
command: ["redis-server", "--appendonly", "yes"]
volumes:
- redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 5s
retries: 20
api:
image: ghcr.io/pona-ekolo/pe-community-api:${PE_COMMUNITY_VERSION:?set PE_COMMUNITY_VERSION in .env}
restart: unless-stopped
env_file: .env
environment:
NODE_ENV: production
API_PORT: "4000"
DATABASE_URL: postgresql://pe:${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}@postgres:5432/pe_community?schema=public
REDIS_URL: redis://redis:6379
WEB_ORIGIN: ${WEB_ORIGIN:?set WEB_ORIGIN in .env}
API_PUBLIC_URL: ${API_PUBLIC_URL:-${WEB_ORIGIN:?set WEB_ORIGIN in .env}}
UPLOADS_DIR: /app/uploads
volumes:
- uploads_data:/app/uploads
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
expose:
- "4000"
healthcheck:
test: ["CMD-SHELL", "wget -qO- http://localhost:4000/health >/dev/null 2>&1"]
interval: 10s
timeout: 5s
retries: 20
worker:
image: ghcr.io/pona-ekolo/pe-community-worker:${PE_COMMUNITY_VERSION:?set PE_COMMUNITY_VERSION in .env}
restart: unless-stopped
env_file: .env
environment:
NODE_ENV: production
DATABASE_URL: postgresql://pe:${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}@postgres:5432/pe_community?schema=public
REDIS_URL: redis://redis:6379
UPLOADS_DIR: /app/uploads
volumes:
- uploads_data:/app/uploads
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
web:
image: ghcr.io/pona-ekolo/pe-community-web:${PE_COMMUNITY_VERSION:?set PE_COMMUNITY_VERSION in .env}
restart: unless-stopped
env_file: .env
environment:
NODE_ENV: production
WEB_PORT: "3000"
INTERNAL_API_URL: ${INTERNAL_API_URL:-http://api:4000}
NEXT_PUBLIC_API_URL: "${NEXT_PUBLIC_API_URL:-/api/v1}"
NEXT_PUBLIC_REALTIME_ORIGIN: "${NEXT_PUBLIC_REALTIME_ORIGIN:-}"
depends_on:
api:
condition: service_healthy
expose:
- "3000"
caddy:
image: caddy:2-alpine
restart: unless-stopped
environment:
APP_DOMAIN: "${APP_DOMAIN:-:80}"
ports:
- "${HTTP_PORT:-80}:80"
- "${HTTPS_PORT:-443}:443"
- "${HTTPS_PORT:-443}:443/udp"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- caddy_config:/config
depends_on:
web:
condition: service_started
api:
condition: service_healthy
volumes:
postgres_data:
redis_data:
uploads_data:
caddy_data:
caddy_config:
Caddyfile
The repository Caddyfile is mounted by the production Compose file. Route order is significant: realtime, API, and uploaded-file requests reach the application before the final interface route.
{$APP_DOMAIN::80} {
encode zstd gzip
handle /socket.io* {
reverse_proxy api:4000
}
handle_path /api/v1/* {
reverse_proxy api:4000
}
handle /uploads/* {
reverse_proxy api:4000
}
handle {
reverse_proxy web:3000
}
}Services
PostgreSQL, Redis, and the application request service have health checks. Startup dependencies wait for the required healthy services. Pending database migrations are applied by the application image before it begins serving requests.
| Service | Role | Persistent data |
|---|---|---|
| postgres | Stores community and application data. | postgres_data |
| redis | Supports background jobs and temporary application state. | redis_data |
| api | Handles requests, authentication, uploads, and realtime connections. | uploads_data |
| worker | Processes background work such as email and notifications. | Shared uploads and database records |
| web | Serves the PE Community application interface. | None |
| caddy | Provides the HTTPS entry point and routes application traffic. | caddy_data, caddy_config |
Networking and HTTPS
Only Caddy needs to be publicly reachable. PostgreSQL, Redis, the application request service, and the interface remain internal to the Compose deployment.
The standard deployment uses the same public origin for the interface, REST requests, uploads, and realtime connections. Published interface images are expected to use `/api/v1` and an empty explicit realtime origin. A custom browser endpoint requires an image built for that endpoint.
| Port | Purpose |
|---|---|
| 80/tcp | HTTP access, redirects, and certificate validation. |
| 443/tcp | HTTPS application traffic. |
| 443/udp | HTTP/3 application traffic. |
Compose-specific environment values
Keep the full configuration in `.env` and use Environment variables as the reference. The values below control Compose identity, the public address, and same-origin browser routing.
COMPOSE_PROJECT_NAME=pe-community
POSTGRES_PASSWORD=<strong-url-safe-database-password>
APP_DOMAIN=community.example.com
WEB_ORIGIN=https://community.example.com
NEXT_PUBLIC_API_URL=/api/v1
NEXT_PUBLIC_REALTIME_ORIGIN=Start and check status
Start the deployment, then confirm that all expected services are running. PostgreSQL, Redis, and the application request service should report healthy.
docker compose -f docker-compose.prod.yml up -d --build
docker compose -f docker-compose.prod.yml psView logs
Use combined logs for the full startup sequence, or follow one service while diagnosing a specific problem.
- api: startup, database migrations, requests, uploads, and realtime errors.
- worker: background jobs, notifications, and email delivery.
- caddy: HTTPS, certificate, and routing errors.
docker compose logs -f
docker compose logs -f api
docker compose logs -f worker
docker compose logs -f caddyRestart and stop
Restart keeps the existing containers and volumes. Stop pauses containers. Down removes containers and the Compose network but retains named volumes unless you add `-v`.
docker compose restart
docker compose restart worker
docker compose stop
docker compose downPersistent data
| Volume | Contains | Backup priority |
|---|---|---|
| postgres_data | Community and application database. | Critical |
| uploads_data | Uploaded files and attachments. | Critical |
| redis_data | Persistent queue and background-job state. | Operational |
| caddy_data | TLS certificates and Caddy runtime state. | Important |
| caddy_config | Caddy configuration runtime state. | Useful |
Keep the project name stable
Set `COMPOSE_PROJECT_NAME=pe-community` before the first start and keep it stable. Docker Compose uses the project name to identify the deployment’s containers, network, and named volumes. Changing it can create a different volume set and make existing data appear missing.
Use `docker compose ps` and `docker volume ls` when you need to inspect the resources associated with a deployment.
Apply updates
After reviewing a source release, rebuild and recreate the application containers. Named volumes remain attached when the Compose project name is unchanged.
Back up the installation before a release that includes database changes. Database migrations run automatically during application startup.
docker compose -f docker-compose.prod.yml up -d --build
docker compose -f docker-compose.prod.yml psSecurity expectations
- Expose only Caddy’s public ports during normal operation.
- Keep PostgreSQL and Redis internal to the deployment.
- Keep `.env` private and never commit secrets.
- Do not mount the Docker daemon socket or grant privileged container access.
- Treat named volumes and backups as sensitive data.
Troubleshooting
- Containers do not start: run `docker compose ps` and `docker compose logs`, then check required values in `.env`.
- The database is unavailable: inspect `docker compose logs postgres` and `docker compose logs api`, then check credentials and persistent storage.
- Background jobs or email are not processed: inspect `docker compose logs worker` and `docker compose logs redis`.
- HTTPS is unavailable: inspect `docker compose logs caddy`, then check DNS, APP_DOMAIN, firewall rules, and ports 80 and 443.
- Realtime or chat does not connect: check Caddy and application logs, then inspect the browser’s WebSocket request.
- Data appears missing after redeployment: confirm COMPOSE_PROJECT_NAME did not change and inspect existing named volumes.