A complete production-grade backend in five minutes — PostgreSQL, Hasura, Auth, Nginx, and optional services, all wired and secured by the nSelf CLI.
What nSelf gives you out of the box
Every nSelf deployment runs the same four required services. Optional services are enabled per project. All config is generated by nself build — never hand-edited.
| Service | Image | Internal Port | External URL |
|---|---|---|---|
| PostgreSQL 16 | postgres:16-alpine | 5432 | Never exposed — internal only |
| Hasura GraphQL | hasura/graphql-engine:v2.44.0 | 8080 | api.{domain} |
| Auth | nhost/hasura-auth:0.36.0 | 4000 | auth.{domain} |
| Nginx | nginx:1.25-alpine | 80 / 443 | All public traffic |
| Service | Enable flag | Port | Purpose |
|---|---|---|---|
| nSelf Admin | ADMIN_ENABLED=true | 3021 | Local GUI companion (never deployed to prod) |
| MinIO | STORAGE_ENABLED=true | 9000 / 9001 | S3-compatible object storage |
| Redis | REDIS_ENABLED=true | 6379 | Caching, sessions, queues |
| Functions | FUNCTIONS_ENABLED=true | 3000 | Serverless runtime for custom logic |
MAIL_ENABLED=true | 1025 / 8025 | SMTP service (16+ providers) | |
| MeiliSearch | SEARCH_ENABLED=true | 7700 | Full-text search engine |
Enabled automatically in staging and production via MONITORING_ENABLED=true. All containers are internal-only — metrics exposed through Grafana at monitor.{ domain }.
Prometheus
Metrics collection and alerting rules
Grafana
Dashboards and alert notifications
Loki
Log aggregation
Promtail
Log shipping agent
Tempo
Distributed tracing (OpenTelemetry)
Alertmanager
Alert routing (PagerDuty, Slack, email)
cAdvisor
Container resource metrics
Node Exporter
Host OS metrics (CPU, RAM, disk, network)
Postgres Exporter
PostgreSQL metrics (connections, locks, queries)
Redis Exporter
Redis metrics (memory, hit rate, commands)
Traffic enters through Nginx (HTTPS/443), which terminates TLS and routes to internal services. All internal services bind to 127.0.0.1 — never exposed directly to the internet.
Internet
│
▼
┌───────────────────────────────────────────────────────┐
│ Nginx (443/80) ← Let's Encrypt TLS │
│ *.{domain} │
└───┬───────┬──────────┬──────────┬────────────────────┘
│ │ │ │
▼ ▼ ▼ ▼
api.{} auth.{} storage.{} search.{} monitor.{}
Hasura Auth MinIO MeiliSearch Grafana
:8080 :4000 :9000 :7700 :3000
│ │
└───┬───┘
▼
PostgreSQL :5432
(internal only, 127.0.0.1)
- np_* tables (nSelf core)
- Plugin tables (flo_, etc.)
- Extensions: pgvector, pg_partman, pg_cron
Also internal:
Redis :6379 ← sessions, queues, plugin cache
Functions :3000 ← serverless custom logic
ping_api :8001 ← license validation, telemetry
Custom Services (CS_1..CS_N):
CS_1 = ping_api (:8001, public as ping.{domain})
CS_N = any user-defined service (40+ templates)
All containers share a single Docker bridge network (nself). Services communicate by container name. No ports are mapped to host interfaces except Nginx (80/443) and Admin (3021, localhost only).
# Inspect the running network
docker network inspect nself
# All service hostnames (container-to-container)
postgres # PostgreSQL
hasura # Hasura GraphQL
auth # nhost/hasura-auth
nginx # Nginx
minio # MinIO (if enabled)
redis # Redis (if enabled)
functions # Functions (if enabled)
mail # Mail service (if enabled)
meilisearch # MeiliSearch (if enabled)
ping-api # CS_1 (always running)
# Port mappings to host (ONLY these are exposed)
nginx: 0.0.0.0:80 → nginx:80
nginx: 0.0.0.0:443 → nginx:443
admin: 127.0.0.1:3021 → admin:3021 # dev onlySecurity invariant
PostgreSQL, Hasura, Auth, Redis, Functions, Mail, and MeiliSearch all bind to 127.0.0.1 inside the container network. They are never reachable from the internet. All external access goes through Nginx with TLS.
Plugins are the extension mechanism for nSelf. Each plugin is a self-contained service with its own Go binary (or Rust for CPU-intensive work), migrations, Hasura metadata, and Nginx config fragments. The CLI composes them into the Docker Compose manifest during nself build.
| Category | Count | License | Install |
|---|---|---|---|
| Free plugins | 25 | MIT | nself plugin install <name> |
| Pro plugins | 87 (94 at v1.1.0) | Source-available, license-gated | nself license set <key> then install |
# 1. Install a plugin (free)
nself plugin install notify
# 1b. Install a pro plugin (requires license)
nself license set nself_pro_xxxxx...
nself plugin install ai
# 2. Build — regenerates docker-compose.yml and nginx config
nself build
# 3. Start (or restart) the stack
nself start
# 4. Check plugin health
nself plugin status
# 5. View plugin logs
nself logs notify
nself logs ai
# 6. Remove a plugin
nself plugin remove notify
nself build
nself startPlugins use the reserved port block 3820–3849 to avoid conflicts with frontend dev servers (3010–3020) and the admin UI (3021).
| Port | Plugin |
|---|---|
| 3820 | idme |
| 3821 | auth |
| 3822 | transactional-email |
| 3823 | nself-vault |
| 3824 | nself-sms |
| 3827 | nself-image |
| 3828 | nself-pdf |
| 3829 | nself-scan (Security-Always-Free) |
| 3830 | nself-stripe |
| 3831–3843 | ɳSentry bundle (v1.1.0) |
| 3203 | nself-geo (grandfathered) |
Beyond plugins, you can add arbitrary services using the custom service slot system. Each CS_N slot is a user-defined container with its own image, environment, and Nginx routing. nSelf dog-foods this — CS_1 = ping_api runs atping.nself.org and handles license validation and CLI telemetry.
# Create a custom service from a template
nself service add myapi --template node
nself service add worker --template python
nself service add ml-server --template fastapi
# Available templates (40+)
nself service templates
# Configure custom service
# Edit .env.dev → set CS_1_IMAGE, CS_1_PORT, CS_1_ENV_*
nself build # regenerates docker-compose.yml
nself start
# View custom service logs
nself logs myapiCS_N configuration pattern
# .env.dev
CS_1_NAME=ping_api
CS_1_IMAGE=nself/ping-api:latest
CS_1_PORT=8001
CS_1_SUBDOMAIN=ping
CS_1_ENV_DATABASE_URL=${DATABASE_URL}
CS_1_ENV_JWT_SECRET=${HASURA_GRAPHQL_JWT_SECRET}nSelf uses a layered environment system. Each layer overrides the one below it. Never hand-edit .env.computed — it is generated by orchestration.sh at build time.
.env.dev ← Team defaults + dev-stub secrets (committed)
↓
.env.local ← Per-developer local overrides (gitignored)
↓
.env.staging ← Staging shared config (committed)
.env.prod ← Production shared config (committed)
↓
.env.secrets ← Real production secrets (gitignored)
↓
.env.computed ← Generated by orchestration.sh (gitignored)
Never hand-edit this file
# Switch target environment
nself env switch local # local development stack
nself env switch staging # staging VPS (167.235.233.65)
nself env switch prod # production VPS (5.75.235.42)
# View current environment
nself env status
# Show resolved config (without secrets)
nself env showRuns the full stack on your machine. The *.local.nself.org wildcard A record resolves to 127.0.0.1 — no /etc/hosts edits needed.
nself start # start all required services
nself start --all # start required + all optional services
nself start --with minio redis mail # selective optional services
# Admin UI (dev only)
nself admin start # opens localhost:3021
# View all service URLs
nself statusMirrors production exactly. Used for integration testing, load testing, and QA sign-off. Hetzner CX23 server at 167.235.233.65.
# Deploy to staging
nself env switch staging
nself build
nself staging deploy
# Or one-line
nself deploy staging
# Run migrations on staging
nself db migrate up --env staging
# Check staging health
nself doctor --env stagingHetzner CX23 server at 5.75.235.42. Always deploy to staging first and verify before promoting to production.
# Deploy to production
nself deploy prod
# Production migration (requires --confirm)
nself db migrate up --env prod --confirm
# Emergency rollback
nself rollback --env prod --steps 1
# Full health check
nself doctor --env prod --deep| Action | Local | Staging | Production |
|---|---|---|---|
| Deploy | ✓ freely | ✓ freely | Confirm intent |
| DB changes | ✓ freely | With caution | Approval required |
| Delete data | ✓ freely | With caution | Never without approval |
| Provision servers | N/A | Confirm cost | Confirm cost + plan |
| Admin UI | ✓ enabled | ✗ disabled | ✗ disabled |
Nginx is the single entry point for all HTTP traffic. Its configuration is generated by nself build from templates in nginx/sites/. The hand-managed fragments in nginx/conf.d/*.conf are included last and take precedence for custom overrides.
| Subdomain pattern | Routes to | Notes |
|---|---|---|
| api.{domain} | hasura:8080 | GraphQL API + REST + WebSocket |
| auth.{domain} | auth:4000 | JWT, OAuth, MFA endpoints |
| storage.{domain} | minio:9000 | S3-compatible file storage |
| search.{domain} | meilisearch:7700 | Full-text search |
| monitor.{domain} | grafana:3000 | Metrics dashboards (internal IP only) |
| ping.{domain} | ping-api:8001 | CS_1 license validation / telemetry |
| {plugin}.{domain} | plugin:38xx | Plugin-specific routes |
| *.{domain} | frontend app | SPA / marketing / docs |
# Test Nginx config before applying
nself nginx test
# Reload Nginx without downtime (zero-downtime config reload)
nself nginx reload
# View generated Nginx config
nself nginx show
# View hand-managed overrides (safe to edit)
ls nginx/conf.d/What NOT to edit
nginx/sites/, docker-compose.yml, and nginx/nginx.conf are generated by nself build. Your changes will be overwritten on the next build. Put custom overrides in nginx/conf.d/*.conf — these are preserved.
nSelf handles TLS automatically. In production, it uses Let's Encrypt with automatic renewal. In local development, it uses mkcert to generate trusted certificates for *.local.nself.org.
# Install mkcert CA (local dev — one-time setup)
nself ssl install
# Issue certs for all configured subdomains (local)
nself ssl generate
# Production — Let's Encrypt (automatic on first deploy)
nself ssl renew # manual renewal if needed
nself ssl status # view expiry dates
# View SSL health
nself doctor --check sslnSelf monitors the stack continuously. Each service has a health check definition in the generated Docker Compose. The CLI exposes a unified health command that aggregates all service states.
# Quick health check
nself health
# Deep diagnostic (checks config, migrations, SSL, plugins, RLS)
nself doctor --deep
# Check specific service
nself health postgres
nself health hasura
nself health auth
# Sample output
# ✓ PostgreSQL healthy (2ms)
# ✓ Hasura healthy (8ms)
# ✓ Auth healthy (5ms)
# ✓ Nginx healthy (1ms)
# ✓ Redis healthy (2ms)
# ⚠ MeiliSearch degraded index rebuilding
# ✗ Functions unhealthy start container manually: nself start functionsContainer restarts
All containers have restart: unless-stopped. Docker restarts failed containers automatically.
Nginx config validation
nself nginx test runs before every reload. Invalid config is rejected without reloading.
Migration safety
nself db migrate up checks checksums before applying. Tampered migrations are blocked.
Plugin circuit breakers
Plugins that fail to start are isolated. Core services keep running.
Security is always free in nSelf — no feature is paywalled. Every install runs the hardening suite automatically.
1. Network
2. Authentication
3. Authorization
4. Data
5. Audit
nSelf ships two distinct isolation mechanisms. Using the wrong one causes silent data leaks. See the Multi-Tenancy page for the full Convention Wall.
Multi-App Isolation
source_account_id TEXT NOT NULL DEFAULT 'primary'
Separates independent apps within one nSelf deploy. Used when you self-host multiple apps on the same stack.
Cloud SaaS Tenancy
tenant_id UUID -- nullable
Separates paying customers in nSelf Cloud. Every table with this column must have a Hasura row filter: x-hasura-tenant-id.
| Server | RAM | vCPU | Suitable for |
|---|---|---|---|
| CX22 | 4 GB | 2 | Dev / small projects |
| CX32 | 8 GB | 4 | Small production (<5K users) |
| CX23 (current nSelf) | 8 GB | 4 | Medium production (5K–50K users) |
| CX33 | 16 GB | 8 | Large production (50K–200K users) |
| CX43 | 32 GB | 16 | High-traffic production |
| CCX53 | 64 GB | 32 (dedicated) | Enterprise / compute-heavy |
# Read replicas (Postgres streaming replication)
nself db replica add --region fsn1 --type CX22
# Hasura read-only endpoint (points to replica)
nself hasura replica enable
# Redis Sentinel for HA
nself service add redis-sentinel --template redis-sentinel
# Load balancer (Hetzner Load Balancer or Nginx upstream)
nself nginx upstream add --server app1:8080 --server app2:8080# Enable PgBouncer (transaction-mode pooling)
nself plugin install pgbouncer
nself build
# Configure pool size (in .env.dev)
PGBOUNCER_POOL_SIZE=25
PGBOUNCER_MAX_CLIENT_CONN=1000
PGBOUNCER_DEFAULT_POOL_MODE=transaction
# Monitor pool stats
nself db pool statusAll backend operations — deploys, migrations, plugin installs, config changes — go through the nSelf CLI. No direct docker compose up, no hand-editing generated files, no side-channel SSH to the server for configuration changes.
| Correct | Never |
|---|---|
| nself start | docker compose up |
| nself build | hand-edit docker-compose.yml |
| nself deploy prod | ssh + manual docker pull |
| nself db migrate up | psql -c "ALTER TABLE..." |
| nself plugin install X | docker run plugin-image |
| nself nginx reload | ssh + nginx -s reload |
| nself logs service | docker logs container |
# Automated backups (configured in .env)
BACKUP_SCHEDULE="0 2 * * *" # daily at 2am UTC
BACKUP_RETENTION_DAYS=30
BACKUP_S3_BUCKET=nself-backups
# Manual backup before risky operations
nself db backup --label pre-migration-v1.2
# List backups
nself db backup list
# Restore from backup
nself db restore backup-2026-05-01-02-00.dump
# Point-in-time recovery (PITR) — requires WAL archiving
nself db pitr --target "2026-05-01 14:30:00 UTC"
# Full stack rebuild from backup (worst case)
nself install --domain my.domain.com
nself db restore backup-2026-05-01.dump
nself build && nself startRecovery time objective (RTO) for a CX23 node with a 10 GB database: under 30 minutes with a fresh Hetzner snapshot + nself db restore.
PostgreSQL
Extensions, indexing, RLS, pgvector
Hasura
GraphQL, RBAC, remote schemas, event triggers
Authentication
JWT, OAuth, MFA, session management
Multi-Tenancy
Convention Wall, source_account_id vs tenant_id
Database Management
Backup, restore, health, shell
Schema Design
Drizzle ORM, np_* conventions, indexes
Migrations
Generate, apply, verify, rollback