NorthRelayNorthRelay
Operators

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-next deployment source.

Deploy NorthRelay's sovereign SMTP infrastructure on your own VPS.


Prerequisites

Before deploying NorthRelay, ensure you have:

RequirementMinimumRecommended
VPS RAM4 GB8 GB
CPU Cores24
Disk40 GB SSD80 GB SSD
OSUbuntu 22.04+Ubuntu 24.04 LTS
Docker24.0+Latest
Docker Composev2.20+Latest
Domain1 domain2 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:

ServiceImagePurpose
appNext.js (custom build)Dashboard and API
postgrespostgres:15-alpinePrimary database
redisredis:7-alpineCaching and rate limiting
stalwartstalwart/mail-serverOutbound SMTP relay and queue
miniominio/minioObject storage (templates, attachments)
cronalpine/curlScheduled task runner
nginxnginx:alpineReverse 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/northrelay

2. Configure Environment Variables

Copy the example environment file and edit it:

cp .env.example .env
nano .env

At 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 -d

4. 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:seed

5. Verify the Deployment

# Check all services are running
docker compose ps

# Check the health endpoint
curl -s https://yourdomain.com/api/health | jq

A 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:ro

Auto-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 | jq

To rebuild all services (after Docker Compose changes):

docker compose down
docker compose build
docker compose up -d

Warning: docker compose down stops 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 app

Health Check Endpoint

The /api/health endpoint provides a quick status check:

curl -s https://yourdomain.com/api/health | jq

Use 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:

TypeNameValuePurpose
Ayourdomain.comYOUR_VPS_IPDashboard
Amail.yourdomain.comYOUR_VPS_IPMail server
MXyourdomain.commail.yourdomain.comInbound mail
TXTyourdomain.comv=spf1 ip4:YOUR_VPS_IP -allSPF
TXT_dmarc.yourdomain.comv=DMARC1; p=quarantine; rua=mailto:dmarc@yourdomain.comDMARC

For DKIM DNS records, see Configuration - DKIM.


Next Steps

On this page