VPS deployment
The project runs as one repository and Compose stack on a Linux VPS. API, guest portal, admin, docs, and PostgreSQL run as separate containers. The current Camel Creatives VPS already has host Nginx on ports 80/443, so this deployment uses Docker Compose behind Nginx. Coolify is installed there, but its proxy is not the active 80/443 listener. This avoids conflicting with existing services.
DNS records
Create A records to the VPS public IPv4 address:
| Name | Destination |
|---|---|
api-towers.camelcreatives.com | VPS public IP |
wateja-towers.camelcreatives.com | Same VPS IP |
towers.camelcreatives.com | Same VPS IP |
docs-towers.camelcreatives.com | Same VPS IP |
Only add AAAA records if IPv6 is correctly configured on the VPS, firewall, and Docker path. DNS must resolve directly to the VPS while Let’s Encrypt issues the certificate.
Firewall and routing
Allow inbound:
- TCP 80 and 443 from the public internet for Nginx and certificate validation.
- UDP 1812 and 1813 from the AP’s known public/VPN RADIUS source address.
The VPS needs outbound HTTPS to provider APIs and ACME, plus outbound UDP 3799 to the AP for CoA. Do not expose TCP 5432 (PostgreSQL), TCP 8000 (Go HTTP), or app container ports. The web containers bind to loopback ports 16440–16443; Nginx proxies to them on the same VPS. RADIUS uses the standard UDP ports 1812/1813, currently filtered at the firewall until the AP’s source address is known.
If the AP uses changing egress IPs, a VPN/static egress address is safer than opening RADIUS widely. If the AP is behind CGNAT, the VPS may not be able to send CoA to its private address; use a routable VPN path or accept that server-driven disconnect is unavailable.
Deploy steps
- Install a supported Docker Engine and Compose plugin on the VPS.
- Clone this repository into
/home/camel/projects/cameltowers. - Copy
.env.exampleto.env, set unique production database, RADIUS, UAM, and encryption secrets, and set file permissions to0600. Never commit.env. - Set admin bootstrap values with
TOWERS_ADMIN_EMAIL,TOWERS_ADMIN_PASSWORD, and origins for the four hostnames. KeepACCESS_CREDENTIAL_KEYstable and backed up. SetPORTAL_HOST_PORT=16441,ADMIN_HOST_PORT=16440, andDOCS_HOST_PORT=16442. - Copy
deploy/nginx/bootstrap.confto/etc/nginx/sites-available/camel-towers, enable the site, and reload Nginx. Request a Let’s Encrypt certificate for all four hostnames with Certbot’s webroot method. - Copy
deploy/nginx/camel-towers.confinto the same Nginx site, installdeploy/nginx/reload-after-cert-renewal.shas a Certbot deploy hook, test the config withnginx -t, then reload Nginx. - Start the stack with the VPS overlay:
docker compose -f docker-compose.yml -f docker-compose.vps.yml up -d --build
docker compose ps
docker compose logs --tail=100 api admin portal docs- Open
https://towers.camelcreatives.com/loginand sign in with the bootstrap account. TOTP is optional and can be enrolled from Team & security. - Set the Abliner callback URL to
https://api-towers.camelcreatives.com/webhooks/abliner. - Verify HTTPS for all four names and API health at
https://api-towers.camelcreatives.com/api/v1/health. Keep paid purchases blocked until the actual AP is registered, its source CIDR is configured, and UAM/RADIUS behavior passes on hardware.
Database and backup
Compose persists PostgreSQL in the towers-postgres named volume. The API’s migrate one-shot service applies lexically sorted SQL migrations and records versions in schema_migrations before API startup.
Back up the database volume using a consistent PostgreSQL backup method. Back up .env/secret-manager values separately, especially ACCESS_CREDENTIAL_KEY, RADIUS and UAM secrets, provider webhook keys, and database credentials. Test restoration before relying on backups.
Local review
The local UI containers bind to loopback ports 3000 (guest), 3001 (admin), and 3002 (docs). The local preview docs URL is http://localhost:3002. The Caddy profile is for a fresh VPS without an existing reverse proxy; this server uses host Nginx and the docker-compose.vps.yml overlay.