Skip to main content
This is the recommended production path for Nudgra OSS. You host the app and Postgres on a VPS, then route HTTPS traffic through Caddy.

Prerequisites

Before you start, prepare:
  • A VPS running Ubuntu or Debian.
  • A domain or subdomain.
  • DNS access for the domain.
  • Google OAuth credentials.
  • Meta app credentials and a webhook verify token.

Point DNS to the VPS

Create an A record for the domain or subdomain.
Type: A
Name: nudgra
Value: YOUR_VPS_PUBLIC_IP
Proxy status: DNS only while issuing the first certificate
If you use Cloudflare, use DNS only for the first certificate setup. After HTTPS works, you can enable the proxy if SSL/TLS mode is Full strict.

Install server packages

On Ubuntu or Debian:
sudo apt update && sudo apt upgrade -y
sudo apt install -y git curl ufw
curl -fsSL https://get.docker.com | sudo sh
sudo usermod -aG docker $USER
newgrp docker
Install Caddy with your preferred package method. If Caddy already serves other apps on the server, keep it running and add a new site block for Nudgra. Open the firewall:
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable

Clone the app

cd /opt
sudo git clone https://github.com/MaikoCode/nudgra-oss nudgra
sudo chown -R $USER:$USER /opt/nudgra
cd /opt/nudgra
cp .env.example .env

Configure production .env

Edit:
nano .env
Set:
SITE_URL=https://your-domain.com
BETTER_AUTH_URL=https://your-domain.com
TRUSTED_ORIGINS=https://your-domain.com

BETTER_AUTH_SECRET=your-generated-secret
TOKEN_ENCRYPTION_KEY=your-generated-token-secret

OPERATOR_EMAIL_ALLOWLIST=your-google-email@gmail.com

GOOGLE_CLIENT_ID=your-google-client-id
GOOGLE_CLIENT_SECRET=your-google-client-secret

META_APP_ID=your-instagram-app-id
META_APP_SECRET=your-instagram-app-secret
META_VERIFY_TOKEN=your-generated-webhook-verify-token
Keep a private copy of .env. It contains the auth and token-encryption secrets needed to decrypt stored OAuth and Instagram tokens.
For production, also change the Postgres password in docker-compose.yml and update DATABASE_URL inside app.environment to match. Recommended: bind the app port to localhost only:
ports:
  - "127.0.0.1:3000:3000"

Start the app

docker compose up -d --build
docker compose logs -f app
The app container runs database migrations before startup. Check the local health endpoint:
curl http://127.0.0.1:3000/api/v1/health

Configure Caddy

Edit:
sudo nano /etc/caddy/Caddyfile
Add a site block:
your-domain.com {
    reverse_proxy 127.0.0.1:3000
}
If your Caddy server is already bound to a specific public IP for other apps, add bind:
your-domain.com {
    bind YOUR_VPS_PUBLIC_IP
    reverse_proxy 127.0.0.1:3000
}
Validate and reload:
sudo caddy fmt --overwrite /etc/caddy/Caddyfile
sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl reload caddy
Check HTTPS:
curl -I https://your-domain.com/api/v1/health
You want a 200 response.

Update a deployment

cd /opt/nudgra
git pull
docker compose up -d --build
docker compose logs -f app

Back up Postgres

Create a compressed backup:
docker compose exec -T postgres pg_dump -U nudgra -d nudgra --format=custom > nudgra.backup
Restore into an empty database:
docker compose exec -T postgres pg_restore -U nudgra -d nudgra --clean --if-exists < nudgra.backup
Back up .env separately.