3.5 KiB
Deployment Guide
This guide covers deploying GcIndexRelay on a VPS using Docker Compose.
Architecture
Host reverse proxy (port 80/443) → localhost:4000
↓
Phoenix container (app) — published port 4000:4000
↓ (internal Docker network only)
AGE/Postgres container (db) — no published ports, volume: pgdata
TLS is terminated by the host-level reverse proxy. The Phoenix app runs behind
it and trusts the X-Forwarded-Proto header to enforce HTTPS.
Prerequisites
- A VPS running Debian/Ubuntu
- Docker and Docker Compose installed
- A domain name pointed at your VPS
1. Install Docker and Docker Compose
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
# Log out and back in for group membership to take effect
Verify: docker compose version
2. Clone the Repository
git clone <repo-url> /opt/gc_index_relay
cd /opt/gc_index_relay
3. Configure Secrets
Copy the example file and fill in your values:
cp .env.example .env
$EDITOR .env
Required values to set in .env:
POSTGRES_PASSWORD— use a strong random passwordSECRET_KEY_BASE— generate with:docker run --rm hexpm/elixir:1.17.3-erlang-27.3.4.7-debian-trixie-20260202-slim \ mix phx.gen.secret # or without Docker: openssl rand -base64 64PHX_HOST— your actual domain name (e.g.relay.example.com)
4. Set Up the Reverse Proxy
The app listens on localhost:4000. Configure nginx or Caddy to forward
traffic and terminate TLS.
Caddy (recommended)
relay.example.com {
reverse_proxy localhost:4000
}
nginx
server {
listen 443 ssl;
server_name relay.example.com;
ssl_certificate /etc/letsencrypt/live/relay.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/relay.example.com/privkey.pem;
location / {
proxy_pass http://localhost:4000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
5. Configure Firewall
Expose only SSH, HTTP, and HTTPS to the public internet:
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
The database port (5432) must not be exposed — it is on an internal Docker network only and never published to the host.
6. Build and Start the Stack
docker compose build
docker compose up -d
On first start, the app container will:
- Wait for the database to pass its health check
- Run any pending Ecto migrations
- Start the Phoenix server
7. Verify the Deployment
# Check container status
docker compose ps
# Health check endpoint
curl http://localhost:4000/health
# Expected: 200 OK, body: "ok"
# API endpoint
curl http://localhost:4000/api/events
# Expected: JSON response
# Confirm database port is NOT accessible from host
curl http://localhost:5432
# Expected: connection refused
Operations
View logs
docker compose logs -f app
docker compose logs -f db
Restart the app
docker compose restart app
Stop everything
docker compose down
Database data is persisted in the pgdata Docker volume and survives
docker compose down. To also delete the data:
docker compose down -v
Update to a new version
git pull
docker compose build
docker compose up -d
Migrations run automatically on startup.