Self-hosting Planscape
Self-hosting Planscape gives you data residency in your country, integration with your existing identity provider, and full control over backups and disaster recovery. It's included in the Enterprise plan and ships as a Docker Compose configuration. This guide walks you through deploying, hardening, and maintaining a self-hosted instance.
Hardware requirements
| Profile | Users | vCPU | RAM | Storage |
|---|---|---|---|---|
| Small (single firm) | β€25 | 4 | 8 GB | 200 GB SSD |
| Medium (multi-firm) | β€100 | 8 | 16 GB | 500 GB NVMe |
| Large | β€500 | 16 | 32 GB | 2 TB NVMe + 4 TB cold |
| Enterprise | β₯500 | HA cluster | β | Discuss with us |
OS: Ubuntu Server 22.04 LTS or 24.04 LTS. Other Linuxes work but we test against Ubuntu.
The component stack
Self-hosted Planscape runs the same code as our cloud. The Compose stack contains:
- API β ASP.NET Core 8 (Planscape.API)
- Hangfire β background worker (Planscape.Worker)
- SignalR β real-time hubs (within API)
- PostgreSQL 16 β primary database
- Redis 7 β cache + SignalR backplane
- MinIO β S3-compatible object store for photos / model files / documents
- Caddy β reverse proxy + automatic HTTPS via Let's Encrypt
Step 1 β Get the Compose bundle
Email onboarding@planscape.co with your Enterprise account ID. You'll get a private link to planscape-selfhost-v<version>.tar.gz with:
docker-compose.yml.env.examplecaddy/Caddyfilescripts/backup.sh,scripts/restore.shREADME.md
Step 2 β Prepare your host
# Install Docker + Compose
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
newgrp docker
# Verify
docker --version
docker compose version
Open ports:
- 80 + 443 β Caddy (HTTP redirect + HTTPS)
- 22 β SSH (your existing access)
Block everything else at the firewall. The internal services (Postgres, Redis, MinIO) bind to the Docker network only.
Step 3 β Configure environment
Copy .env.example to .env and fill in:
# Required
DOMAIN=planscape.yourfirm.co.ug
ADMIN_EMAIL=admin@yourfirm.co.ug
POSTGRES_PASSWORD=<long-random>
REDIS_PASSWORD=<long-random>
MINIO_ROOT_PASSWORD=<long-random>
JWT_SECRET=<64-char-random>
LICENSE_KEY=<from-onboarding-email>
# Optional
SMTP_HOST=smtp.brevo.com
SMTP_PORT=587
SMTP_USER=...
SMTP_PASS=...
SMTP_FROM=planscape@yourfirm.co.ug
# Mobile push (optional β leave blank to disable)
EXPO_ACCESS_TOKEN=
# Backup target (recommended)
S3_BACKUP_ENDPOINT=https://...
S3_BACKUP_BUCKET=planscape-backups
S3_BACKUP_KEY=...
S3_BACKUP_SECRET=...
openssl rand -base64 48 for each. Don't reuse the example values.
Step 4 β DNS
Point your chosen domain at the host's public IP:
A planscape.yourfirm.co.ug β 1.2.3.4
Caddy fetches a Let's Encrypt certificate the first time it starts. The DNS A record must resolve before you start the stack or the cert acquisition will fail.
Step 5 β Start the stack
docker compose up -d
docker compose logs -f api # watch startup
First boot does these things in order:
- Postgres starts and initialises an empty database.
- API runs
dotnet ef database updateapplying all schema migrations. - API seeds the default tenant + an initial admin user.
- MinIO buckets are created (
planscape-photos,planscape-models,planscape-docs). - Hangfire schedules the recurring jobs (trial expiry, backups).
- Caddy acquires HTTPS certificate.
Open https://planscape.yourfirm.co.ug and log in with the admin credentials emailed to you when the bundle was generated. Change the password immediately.
Step 6 β Configure backup
The default scripts/backup.sh dumps Postgres, snapshots the MinIO buckets, and uploads both to your S3-compatible target.
Add a cron entry:
0 2 * * * /opt/planscape/scripts/backup.sh >> /var/log/planscape-backup.log 2>&1
For point-in-time recovery, enable WAL archiving on Postgres (see the bundle README). Without WAL, you can restore to last-night's backup only.
Step 7 β Configure SSO (optional)
Self-hosted Planscape supports SAML 2.0 + OIDC out of the box. From the admin dashboard: Settings β SSO. Walk-throughs:
Day-to-day operations
Health check
curl https://planscape.yourfirm.co.ug/health
# Expected: {"status":"healthy","version":"v2.1.4","db":"up","redis":"up","minio":"up"}
Logs
docker compose logs --tail=200 -f api
docker compose logs --tail=200 -f worker
Logs are structured JSON. Ship them to Loki / Elastic / Datadog with your log shipper of choice.
Restart a service
docker compose restart api
Database access
docker compose exec db psql -U planscape -d planscape
Upgrading
We release updates monthly. To upgrade:
# Backup first
./scripts/backup.sh
# Pull new images
docker compose pull
# Apply
docker compose up -d
# Verify migrations applied + health
docker compose logs api | grep "Migrations applied"
curl https://planscape.yourfirm.co.ug/health
Major version upgrades (e.g. v2.x to v3.x) may require a migration script β we send release notes with each Enterprise newsletter.
Monitoring
The API exposes Prometheus metrics on /metrics (internal port 8080). Useful starting metrics:
planscape_active_usersβ concurrent usersplanscape_db_connection_pool_utilizationβ DB pool pressureplanscape_hangfire_pending_jobsβ background job queue depthplanscape_storage_bytes_usedβ MinIO consumption
Sample Grafana dashboard JSON is in the bundle (monitoring/grafana-planscape.json).
Hardening checklist
- Firewall closes everything except 80/443/22
- SSH keys only (password auth disabled)
- Postgres / Redis / MinIO never exposed to the public network
- All
.envsecrets are unique random strings - Backups run nightly and are stored off-host
- Caddy auto-renews certs every 60 days (default behaviour)
- OS unattended-upgrades enabled for security patches
- Database password rotation policy in place
- Admin accounts require 2FA (enforced in Settings β Security)
Migrating from cloud to self-hosted
If you've been on the cloud plan and want to bring data on-prem:
- Tell us 14 days in advance β we schedule an export window.
- We generate an export bundle (Postgres dump + MinIO archive + audit log).
- We ship the bundle to your team via encrypted transfer.
- Stand up the self-host stack with an empty database.
- Run
./scripts/restore.sh planscape-export.tar.gz. - Verify; switch DNS for your users; we revoke the cloud instance after a 30-day quarantine.
Getting help
Enterprise support hours: 24Γ5 (MonβFri) with 1-hour response SLA. Out of hours: best-effort via WhatsApp.
Channel: enterprise@planscape.co + a dedicated Slack Connect channel set up at onboarding.