You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

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 password
  • SECRET_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 64
    
  • PHX_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.

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:

  1. Wait for the database to pass its health check
  2. Run any pending Ecto migrations
  3. 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.