GGlyphDocs

deployment

Reverse Proxy and SSL

Put Glyph behind Caddy or nginx for HTTPS and clean URLs.

Out of the box, Glyph listens on :3000 (frontend) and :8000 (backend) over plain HTTP. For production, terminate TLS at a reverse proxy and route both services to public hostnames.

Two subdomains, both HTTPS:

SubdomainRoutes to
glyph.example.comlocalhost:3000 (frontend)
api.glyph.example.comlocalhost:8000 (backend)

A single hostname with path-based routing (/api/* → backend) is also workable but harder to maintain.

Caddy (easiest)

Caddy auto-provisions Let's Encrypt certificates. Install Caddy on your server, then:

/etc/caddy/Caddyfile:

glyph.example.com {
    reverse_proxy localhost:3000
}
 
api.glyph.example.com {
    reverse_proxy localhost:8000
}

Reload:

sudo systemctl reload caddy

Caddy obtains certificates on first request. DNS A records for both subdomains must point to your server first.

nginx

# /etc/nginx/sites-available/glyph
server {
    listen 80;
    server_name glyph.example.com api.glyph.example.com;
    return 301 https://$host$request_uri;
}
 
server {
    listen 443 ssl http2;
    server_name glyph.example.com;
 
    ssl_certificate     /etc/letsencrypt/live/glyph.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/glyph.example.com/privkey.pem;
 
    client_max_body_size 200M;  # for large local-upload imports
 
    location / {
        proxy_pass http://localhost:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_cache_bypass $http_upgrade;
    }
}
 
server {
    listen 443 ssl http2;
    server_name api.glyph.example.com;
 
    ssl_certificate     /etc/letsencrypt/live/api.glyph.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/api.glyph.example.com/privkey.pem;
 
    client_max_body_size 500M;  # backend handles file uploads
 
    location / {
        proxy_pass http://localhost:8000;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_read_timeout 300s;
    }
}

Get certificates with Certbot:

sudo certbot --nginx -d glyph.example.com -d api.glyph.example.com

Enable and reload:

sudo ln -s /etc/nginx/sites-available/glyph /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

Updating Glyph configuration

After setting up the proxy, update .env:

FRONTEND_URL=https://glyph.example.com
NEXT_PUBLIC_API_URL=https://api.glyph.example.com
APP_URL=https://api.glyph.example.com
CORS_ORIGINS=https://glyph.example.com

Restart the stack:

docker compose -f docker-compose.prod.yml up -d

NEXT_PUBLIC_API_URL is baked into the frontend image at build time in some setups. With the prod image from ghcr.io, it's read at runtime - but if you build locally, rebuild after changing it.

Updating OAuth callbacks

If you've configured GitHub OAuth or GitLab OAuth, update the callback URL on each provider to match your new HTTPS API URL:

  • GitHub: https://api.glyph.example.com/api/auth/github/callback
  • GitLab: https://api.glyph.example.com/api/auth/gitlab/callback

A mismatch results in a redirect URI error during sign-in.

Firewall

Once the proxy is up, you can close the host ports 3000 and 8000 to the public internet:

sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw deny 3000/tcp
sudo ufw deny 8000/tcp

The proxy connects to localhost:3000 / localhost:8000 from inside the host, so they only need to be reachable locally.

Troubleshooting

  • 502 Bad Gateway - Glyph containers aren't running or aren't healthy. Check docker compose ps.
  • CORS errors in the browser - CORS_ORIGINS doesn't include your frontend URL.
  • Login works but redirects to localhost:8000 - APP_URL still points at localhost. Update and restart.
  • WebSocket connection fails - nginx config missing Upgrade/Connection headers.