Self-hosting Guide
This guide covers a production-grade OpenCan deployment: HTTPS via a reverse proxy, persistent data volumes, backups, and update procedures. For a quick local run, see Getting Started.
Prerequisites
- A Linux server (Ubuntu 22.04 LTS recommended) with at least 1 GB RAM
- Docker Engine 24+ and Docker Compose v2
- A domain name with an A record pointing to your server's IP
- A Resend account (or any SMTP provider) for transactional email
- Ports 80 and 443 open in your firewall
Docker Compose setup
1. Clone the repository
git clone https://github.com/sriramgopalan/opencan.git
cd opencan 2. Configure environment variables
cp .env.example .env
nano .env # or your editor of choice Set every required variable. In particular:
NEXT_PUBLIC_APP_URL— your public domain, e.g.https://feedback.example.comAUTH_URL— same value asNEXT_PUBLIC_APP_URLAUTH_SECRET—openssl rand -base64 32IP_HASH_SECRET—openssl rand -hex 32MINIO_USE_SSL— set totruein productionNODE_ENV— set toproduction
See the full Environment Variables reference for every option.
3. Start the stack
docker compose up -d
Docker Compose brings up four services: app (Next.js), postgres, redis, and minio. On first boot, database migrations run automatically inside the app container.
4. Verify everything is running
docker compose ps
docker compose logs app --tail=50 The app listens on port 3000 by default. You'll put a reverse proxy in front of it for HTTPS.
HTTPS with a reverse proxy
Run a reverse proxy on your server to terminate TLS and forward traffic to the app container.
Option A — Caddy (recommended)
Caddy handles certificate provisioning automatically via Let's Encrypt.
Create a Caddyfile alongside your docker-compose.yml:
feedback.example.com {
reverse_proxy app:3000
} Add Caddy to your docker-compose.yml:
services:
caddy:
image: caddy:2-alpine
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- caddy_config:/config
networks:
- opencan
volumes:
caddy_data:
caddy_config: Option B — nginx + Certbot
sudo apt install nginx certbot python3-certbot-nginx -y
sudo certbot --nginx -d feedback.example.com Add an nginx site config to proxy to localhost:3000.
Environment variables reference
See the dedicated Environment Variables page for a full table of every variable, whether it is required, and what it does.
First admin user
Register an account at /auth/register, then promote it to admin:
docker compose exec postgres psql -U postgres -d opencan -c \
"UPDATE \"User\" SET role = 'ADMIN' WHERE email = 'your@email.com';" Log in and navigate to /admin to access the admin dashboard.
Production considerations
Persistent volumes
Docker Compose creates named volumes for Postgres data, Redis data, and MinIO objects. These persist across container restarts. Never remove volumes without backing up first.
Backups
Database# Dump
docker compose exec postgres pg_dump -U postgres opencan | gzip > opencan-$(date +%Y%m%d).sql.gz
# Restore
gunzip -c opencan-20260101.sql.gz | docker compose exec -T postgres psql -U postgres opencan MinIO objects Use the MinIO Client (mc) to mirror your bucket to an external location:
mc alias set local http://localhost:9000 MINIO_ACCESS_KEY MINIO_SECRET_KEY
mc mirror local/your-bucket-name s3/backup-bucket Schedule both backup commands via cron to run nightly. Store backups off-server.
Updates
git pull origin main
docker compose pull app
docker compose up -d app Migrations run automatically on container startup. Review the release notes before upgrading to catch any breaking changes.
Resource limits
For small teams (under 50 users), 1 vCPU and 1 GB RAM is sufficient. For larger deployments, consider scaling Postgres to a managed database (Supabase, Railway, Neon) and Redis to a managed cache (Upstash). MinIO can be replaced with any S3-compatible storage.
Session security
OpenCan includes a session blocklist backed by Redis. Revoking a user in the admin dashboard immediately invalidates their session without waiting for token expiry. This requires REDIS_URL to be set correctly.