Self-hosting Planscape

πŸ“– 18-minute read Β· Advanced Β· Last updated 2026-05-23

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.

Self-hosting is for IT teams. If your firm doesn't have someone comfortable with Linux, Docker, PostgreSQL, and HTTPS certificates, the cloud-hosted plan is the right choice. We don't provide remote-hands setup as part of self-hosting; the Enterprise plan includes consulting hours but you supply the infrastructure access.

Hardware requirements

ProfileUsersvCPURAMStorage
Small (single firm)≀2548 GB200 GB SSD
Medium (multi-firm)≀100816 GB500 GB NVMe
Large≀5001632 GB2 TB NVMe + 4 TB cold
Enterpriseβ‰₯500HA 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:

Step 1 β€” Get the Compose bundle

Email onboarding@planscape.build with your Enterprise account ID. You'll get a private link to planscape-selfhost-v<version>.tar.gz with:

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:

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=...
Generate random secrets properly. Use 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:

  1. Postgres starts and initialises an empty database.
  2. API runs dotnet ef database update applying all schema migrations.
  3. API seeds the default tenant + an initial admin user.
  4. MinIO buckets are created (planscape-photos, planscape-models, planscape-docs).
  5. Hangfire schedules the recurring jobs (trial expiry, backups).
  6. 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:

Sample Grafana dashboard JSON is in the bundle (monitoring/grafana-planscape.json).

Hardening checklist

Migrating from cloud to self-hosted

If you've been on the cloud plan and want to bring data on-prem:

  1. Tell us 14 days in advance β€” we schedule an export window.
  2. We generate an export bundle (Postgres dump + MinIO archive + audit log).
  3. We ship the bundle to your team via encrypted transfer.
  4. Stand up the self-host stack with an empty database.
  5. Run ./scripts/restore.sh planscape-export.tar.gz.
  6. 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.build + a dedicated Slack Connect channel set up at onboarding.