Docker
Run ParallelPath in hardened Ubuntu 24.04 containers with the same security model as a direct install. The interactive docker/setup.sh does everything.
Requirements
Ubuntu Server 24.04 LTS or newer (other Linux hosts usually work but are untested), 2 GB+ RAM, 15 GB+ free disk, ports 80/443 (or an existing nginx on the host). Docker and Compose v2 are installed from Ubuntu's own repositories if missing.
Install
tar xzf parallelpath-VERSION.tar.gz
cd parallelpath
sudo bash docker/setup.sh
The script asks for:
| Question | Notes |
|---|---|
| Domain | DNS is checked against this host. |
| How it's reached | 1) the proxy container listens on 80/443 directly, or 2) behind this host's existing nginx: the container listens on 127.0.0.1:8443 and a host nginx site (generated, tested, rolled back on error) forwards to it over TLS. |
| HTTPS | Let's Encrypt, reuse an existing certificate (wildcards detected), or temporary self-signed. |
| Encrypted volume size | Required - all container data lives on a LUKS2 volume unlocked at boot with a systemd-creds sealed key. |
| Admin account, IP allow-list, calls relay | As for the direct installer. |
| Recovery passphrase | Encrypts the recovery-key bundle (/root/parallelpath-recovery.ppexport, also copied to the backup-copies folder). |
How the containers are isolated
| Container | Network | Runs as | Notes |
|---|---|---|---|
| db (PostgreSQL 16) | none | uid 10012 | Unix socket only, scram passwords from Docker secrets, data checksums, superuser password random and discarded at setup |
| app | outbound only, no published ports | uid 10001 | Serves on a unix socket shared only with the proxy; refuses to start unless its data is on an encrypted volume |
| proxy (nginx) | published 80/443 | uid 10013 | TLS 1.2/1.3, HSTS, rate limits, anonymised logs, admin allow-list |
| helper | outbound only | uid 10001 | Scheduled backups, verify, download preparation, SFTP copies, metrics |
| migrate | none | uid 10001 | One-shot schema setup as the owner role, then exits |
| turn (optional) | host (needs a UDP range) | turnserver | Private networks blocked; short-lived credentials |
Every container: read-only root filesystem, all Linux capabilities dropped, no-new-privileges, Docker's default seccomp and AppArmor profiles, memory/CPU/PID limits, healthchecks and an init process. Images are multi-stage (no compilers or package caches), fully updated at build, and non-root.
Nothing crosses a network in clear: browser to proxy is TLS; proxy to app and app to database are Unix sockets on private tmpfs volumes (in mode 2, host nginx to the proxy is TLS on loopback).
Secrets (master key, backup key, database passwords, TURN secret) are Docker secret files with per-container ownership - never environment variables or image layers.
Everyday commands
cd /path/to/parallelpath/docker
sudo docker compose ps # status / health
sudo docker compose logs -f app # application log
sudo docker compose run --rm app verify-audit # check the audit chain
sudo docker compose run --rm app create-admin # another admin account
sudo bash setup.sh # re-run: certificates, settings (keeps data and keys)
Backups are managed in the admin portal (Backups): schedule, retention, copies to the backup-copies folder (point PP_BACKUP_COPY_DIR in docker/.env at a mounted NAS/USB disk) or SFTP, verify and download.
Upgrading
cd parallelpath-NEW && sudo cp -a ../parallelpath/docker/{secrets,config,certs,.env,state.env} docker/
sudo docker compose -f docker/compose.yml up -d --build
Notes
- Docker publishes ports itself and bypasses ufw - only the ports above are published.
- System metrics in the admin portal reflect the helper container's view of the host.
- fail2ban runs on the host if you want it (read the proxy's log with
docker compose logs proxy).