Docker Installation
Procedure for a fresh Ubuntu server. The existing servers (51.75.79.112 and 51.75.77.220) are already installed; this page is for rebuilding a server or preparing a new one.
1. Install Docker Engine and Compose v2
sudo apt update && sudo apt install -y ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
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
sudo apt update && sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
sudo usermod -aG docker ubuntu # then log in again
docker compose version # must show v2.x
Do not install docker-compose (v1): it fails with Docker Engine 28.
2. Harden the server
sudo apt install -y fail2ban unattended-upgrades
# SSH: key only
sudo sed -i 's/^#\?PasswordAuthentication .*/PasswordAuthentication no/' /etc/ssh/sshd_config
sudo systemctl restart ssh
Internal service ports are bound to 127.0.0.1 in the compose files: only 22, 80 and 443 should be reachable from the Internet.
3. Put the project in place
The code is not cloned on the server: it is sent from the admin workstation (see Deployment). Expected layout in ~/pch-sig:
~/pch-sig/
├── docker-compose.prod.yml # or docker-compose.preprod.yml
├── .env.prod # secrets read by Compose (chmod 600)
├── backend/ # sources + backend/.env (copied into the image)
│ └── config/jwt/ # server-specific JWT keys (never overwrite)
├── frontend/build/ # React build (bind-mounted)
├── documentation/build/ # Docusaurus build (bind-mounted)
├── nginx/frontend.conf # frontend Nginx (CSP)
└── monitoring/ # Loki / Promtail configuration
4. Fill in the secrets
Create ~/pch-sig/.env.prod (chmod 600) with the values from the secrets manager: see Configuration. Never copy a .env from one server to another without reviewing every value.
5. Build and start
cd ~/pch-sig
docker compose -f docker-compose.prod.yml build backend
docker compose -f docker-compose.prod.yml up -d
docker compose -f docker-compose.prod.yml ps
-fWithout -f, Compose reads compose.yaml (Symfony skeleton) and starts a database with the default password.
The backend entrypoint creates the JWT keys when missing (pre-production; in production create them on the host first, see JWT authentication), waits for PostgreSQL, runs the migrations and warms the cache. Follow it with:
docker compose -f docker-compose.prod.yml logs -f backend
6. Check
docker exec pch_backend curl -s http://127.0.0.1:8000/api/health
curl -s -o /dev/null -w "%{http_code}\n" https://bemba.ucp-pch.org/
Both must return 200 (the first certificate can take a minute). Continue with the first start.