Deployment Guide
Deploy NorthRelay on your own infrastructure with Docker Compose
Deployment Guide
Mailbox version boundary: the hosted mailbox service uses Stalwart Community 0.16.23 with native account administration. The application sending instance has a separate configuration and lifecycle. Older SQL-directory and container examples below must not be applied to the production mailbox; see Hosted email access and the platform
infra/mailbox-nextdeployment source.
Deploy NorthRelay's sovereign SMTP infrastructure on your own VPS.
Prerequisites
Before deploying NorthRelay, ensure you have:
| Requirement | Minimum | Recommended |
|---|---|---|
| VPS RAM | 4 GB | 8 GB |
| CPU Cores | 2 | 4 |
| Disk | 40 GB SSD | 80 GB SSD |
| OS | Ubuntu 22.04+ | Ubuntu 24.04 LTS |
| Docker | 24.0+ | Latest |
| Docker Compose | v2.20+ | Latest |
| Domain | 1 domain | 2 domains (corporate + system) |
You also need:
- A public IPv4 address with PTR record configured
- Ports 25, 443, 587, 993 open in your firewall
- DNS control for your domain(s)
Architecture Overview
NorthRelay runs as a Docker Compose stack with the following services:
| Service | Image | Purpose |
|---|---|---|
app | Next.js (custom build) | Dashboard and API |
postgres | postgres:15-alpine | Primary database |
redis | redis:7-alpine | Caching and rate limiting |
stalwart | stalwart/mail-server | Outbound SMTP relay and queue |
minio | minio/minio | Object storage (templates, attachments) |
cron | alpine/curl | Scheduled task runner |
nginx | nginx:alpine | Reverse proxy with TLS termination |
For the full production topology with the single relay route, see Stalwart Setup.
Initial Deployment
1. Clone the Repository
git clone https://github.com/northrelay/northrelay-platform.git /opt/northrelay
cd /opt/northrelay2. Configure Environment Variables
Copy the example environment file and edit it:
cp .env.example .env
nano .envAt minimum, set these variables:
# Database
DATABASE_URL="postgresql://north_user:YOUR_SECURE_PASSWORD@postgres:5432/northrelay"
# Authentication
NEXTAUTH_SECRET="$(openssl rand -base64 32)"
NEXTAUTH_URL="https://yourdomain.com"
NEXT_PUBLIC_APP_URL="https://yourdomain.com"
# Stalwart Relay (outbound)
STALWART_API_URL="http://stalwart:8080"
STALWART_API_TOKEN="your-stalwart-admin-token"
# Email sender identity
SMTP_FROM="no-reply@yourdomain.com"
SMTP_FROM_NAME="YourService"See Configuration for the full environment variable reference.
3. Start the Stack
docker compose up -d4. Initialize the Database
# Push the Prisma schema
docker compose exec app npx prisma db push
# Generate Prisma client
docker compose exec app npx prisma generate
# Seed with admin account and system domains
docker compose exec app npm run db:seed5. Verify the Deployment
# Check all services are running
docker compose ps
# Check the health endpoint
curl -s https://yourdomain.com/api/health | jqA healthy response looks like:
{
"status": "ok",
"database": "connected",
"timestamp": "2026-03-19T00:00:00.000Z"
}SSL/TLS with Let's Encrypt
NorthRelay uses Nginx as a reverse proxy with automatic TLS certificates from Let's Encrypt.
Obtain Certificates
# Install certbot
apt install certbot python3-certbot-nginx
# Generate certificate
certbot certonly --standalone -d yourdomain.com -d www.yourdomain.com
# Certificates are stored in /etc/letsencrypt/live/yourdomain.com/Configure Nginx
The Nginx configuration in the Docker Compose stack expects certificates mounted at:
/etc/nginx/ssl/fullchain.pem/etc/nginx/ssl/privkey.pem
Mount your Let's Encrypt certificates in docker-compose.yml:
nginx:
volumes:
- /etc/letsencrypt/live/yourdomain.com/fullchain.pem:/etc/nginx/ssl/fullchain.pem:ro
- /etc/letsencrypt/live/yourdomain.com/privkey.pem:/etc/nginx/ssl/privkey.pem:roAuto-Renewal
# Test renewal
certbot renew --dry-run
# Add cron for auto-renewal
echo "0 3 * * * certbot renew --quiet && docker compose -f /opt/northrelay/docker-compose.yml restart nginx" | crontab -Updating and Redeploying
To deploy a new version:
cd /opt/northrelay
# Pull latest code
git pull origin main
# Rebuild the app container
docker compose build app
# Restart with new image
docker compose up -d app
# If database schema changed, run migrations
docker compose exec app npx prisma db push
docker compose exec app npx prisma generate
# Verify
curl -s https://yourdomain.com/api/health | jqTo rebuild all services (after Docker Compose changes):
docker compose down
docker compose build
docker compose up -dWarning:
docker compose downstops all services including the database. Ensure you have backups before running this in production.
Checking Logs After Deploy
# App logs (most common)
docker compose logs app --tail 100
# All service logs
docker compose logs --tail 50
# Follow logs in real time
docker compose logs -f appHealth Check Endpoint
The /api/health endpoint provides a quick status check:
curl -s https://yourdomain.com/api/health | jqUse this for:
- Load balancer health checks
- Uptime monitoring (e.g., UptimeRobot, Pingdom)
- Deployment verification scripts
DNS Records
At minimum, configure these DNS records for your domain:
| Type | Name | Value | Purpose |
|---|---|---|---|
| A | yourdomain.com | YOUR_VPS_IP | Dashboard |
| A | mail.yourdomain.com | YOUR_VPS_IP | Mail server |
| MX | yourdomain.com | mail.yourdomain.com | Inbound mail |
| TXT | yourdomain.com | v=spf1 ip4:YOUR_VPS_IP -all | SPF |
| TXT | _dmarc.yourdomain.com | v=DMARC1; p=quarantine; rua=mailto:dmarc@yourdomain.com | DMARC |
For DKIM DNS records, see Configuration - DKIM.
Next Steps
- Configuration Guide - Full environment variable and service configuration reference
- Stalwart Setup - Detailed Stalwart mail server configuration
- Monitoring - Set up health checks and alerting
- Runbooks - Operational procedures for common issues