Perspective V Docs

Step By Step Setup Guide

This runbook is for the fresh VPS reset (Ubuntu 24.04 + Docker + Docker Compose + zsh).

Current runtime guide set:

  • runtime-setup-master-guide.md
  • runtime-environments-guide.md
  • runtime-ci-guide.md
  • runtime-wrapper-scripts-guide.md
  • stack-edge-guide.md
  • stack-platform-guide.md
  • stack-operations-guide.md
  • stack-gitea-guide.md
  • stack-databases-guide.md
  • stack-netbird-guide.md
  • stack-gateway-guide.md
  • stack-object-storage-guide.md
  • service-stacks-guide.md

This file is kept as the original phase runbook. Use the guide set above for the current runtime layout and wrapper-script workflow.

Step By Step Setup Guide

This runbook is for the fresh VPS reset (Ubuntu 24.04 + Docker + Docker Compose + zsh).

Target State

  • Dedicated edge Traefik from runtime/stacks/infrastructure/edge is the only public entrypoint on ports 80 and 443 from day one.
  • NetBird is deployed using the official quickstart script in Existing Traefik mode.
  • Public website/API traffic remains internet-accessible on ports 80 and 443.
  • The package registry and feeds are now served by Gitea at gitea.perspective-v.com, internet-accessible for CI restore/install. The legacy nuget.perspective-v.com and npm.perspective-v.com feed hosts have been retired.
  • Package publish and pull are controlled by Gitea access tokens (scopes write:package / read:package).
  • Databases (MSSQL, PostgreSQL, MySQL, and MongoDB) are reachable only over NetBird VPN.
  • Redis developer endpoint redis.perspective-v.com is reachable only over NetBird VPN.
  • Production containers keep using internal Redis host redis on the platform network.
  • Admin panels (Portainer and Traefik dashboard) are reachable only over NetBird VPN.
  • Admin panel host routes can exist publicly but access is blocked unless source traffic comes from NetBird ranges.
  • DB and DB UI hostnames remain public DNS records in this phase; a separate NetBird-only DNS zone is not required.
  • Keep runtime lightweight for 4 vCPU / 8 GB RAM.

Current Deployment Snapshot (2026-04-16)

  • Edge stack is running with Traefik v3.6, Portainer BE, and Kener v4.0.16.
  • Traefik Docker provider compatibility is pinned with DOCKER_API_VERSION=1.40.
  • Let's Encrypt resolver is defined in runtime/stacks/infrastructure/edge/traefik/traefik.yml and active.
  • NetBird is live in Existing Traefik mode and reachable at netbird.perspective-v.com.
  • Traefik dashboard is NetBird-only via source CIDR allowlist plus basic auth middleware.
  • Portainer is NetBird-only via source CIDR allowlist and uses built-in app authentication.
  • Kener is publicly reachable and uses built-in app authentication.
  • Active NetBird artifacts are tracked in runtime/stacks/infrastructure/netbird (docker-compose.yml, config.yaml) and runtime/environments/vps/infrastructure/netbird/netbird.env.
  • Shared stacks were deployed in 13.1 order (platform -> operations -> package registry).
  • Watchtower is running from runtime/stacks/infrastructure/operations with DOCKER_API_VERSION=1.40 and schedule 0 0 3 * * 0 (Sunday 03:00 UTC).
  • The package registry (Docker + npm + NuGet) is now Gitea at gitea.perspective-v.com, owner org perspective-v. The legacy docker-registry, registry-admin, ProGet, BaGet and Verdaccio services have been RETIRED and removed; their old subdomains (registry.perspective-v.com, registry-admin.perspective-v.com, proget.perspective-v.com, nuget.perspective-v.com, npm.perspective-v.com) now 404.
  • Gitea auth uses a Gitea username + access token (token as password): write:package to push, read:package to pull.
  • Database engines (MSSQL, PostgreSQL, MySQL, MongoDB) are deployed from runtime/stacks/infrastructure/databases.
  • DB management UIs (pgAdmin, phpMyAdmin, mongo-express) are deployed behind NetBird-only Traefik routes and use built-in UI authentication.
  • DB and Redis firewall cutover was applied with interface-scoped UFW rules on wt0.
  • Redis is published on host port 6379 for developer access via redis.perspective-v.com while production containers use internal host redis.
  • Final client-side validation still requires checks from dedicated NetBird and non-NetBird machines.

1) Set Variables

export PLAN_DIR="/opt/docs"
export NETBIRD_DOMAIN="netbird.perspective-v.com"

If this docs repository is not yet on the VPS, copy it to $PLAN_DIR before continuing.

2) Install Small Prerequisites and Verify Docker

sudo apt update
sudo apt install -y curl jq ufw netcat-openbsd dnsutils
docker --version
docker compose version

3) Validate NetBird DNS

dig +short ${NETBIRD_DOMAIN} A
dig +short ${NETBIRD_DOMAIN} AAAA

Expected: both commands return your VPS public IPv4/IPv6.

4) SSH Lockout Prevention Checklist (Do This First)

Before changing any firewall rule:

  • Keep your current SSH session open.
  • Open a second SSH session and keep it ready for reconnect testing.
  • Confirm your real SSH port. If you changed SSH from 22 to a custom port, you must allow that custom port before enabling UFW.
  • Confirm the OpenSSH app profile exists in UFW; if it does not, allow SSH using the actual port number instead of relying on the profile name.
  • Keep Contabo web console access ready as out-of-band recovery.
  • Do not close your active SSH session until a new SSH login is confirmed after firewall changes.

5) Apply Base Firewall Policy (SSH-Safe Order)

# Allow SSH first. If you use a custom SSH port, allow that port as well.
sudo ufw allow OpenSSH

sudo ufw default deny incoming
sudo ufw default allow outgoing

# Keep public web ports open for internet-facing websites/APIs.
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp

# NetBird STUN/TURN.
sudo ufw allow 3478/udp comment 'NetBird STUN/TURN'

# Remove old broad rules if they exist.
sudo ufw --force delete allow 1433/tcp || true
sudo ufw --force delete allow 5432/tcp || true
sudo ufw --force delete allow 3306/tcp || true
sudo ufw --force delete allow 27017/tcp || true
sudo ufw --force delete allow 6379/tcp || true
sudo ufw --force delete allow 9443/tcp || true
sudo ufw --force delete allow 3001/tcp || true
sudo ufw --force delete allow 8082/tcp || true

sudo ufw --force enable
sudo ufw status numbered

Important:

  • Do not remove 80/tcp or 443/tcp if you plan to host public websites/APIs.
  • DB ports (1433, 5432, 3306, and 27017) and Redis port (6379) are restricted to NetBird via interface-scoped UFW rules.
  • Admin routes are restricted to NetBird by Traefik middleware source-range allowlist.

6) SSH Verification Gate (Do Not Skip)

After enabling UFW:

  • Start a fresh SSH connection from a new terminal.
  • Only continue if the new SSH login succeeds.
  • If login fails, keep using the original open SSH session to correct rules immediately.
  • If both sessions fail, use Contabo web console to recover access.

7) Emergency Recovery If SSH Access Breaks

  • Use Contabo console to log in directly to the VPS.
  • Temporarily disable or correct UFW rules to restore SSH.
  • Re-apply firewall changes only after confirming the correct SSH port allow rule.

8) Deploy Dedicated Edge Stack (Day One)

Run the shared edge stack before NetBird so your production reverse proxy baseline is in place from the start.

cd "$PLAN_DIR/runtime/stacks/infrastructure/edge"

# One shared network for all Traefik-routed stacks.
docker network create proxy || true

# ACME storage for Let's Encrypt.
sudo mkdir -p /var/lib/traefik/letsencrypt
sudo touch /var/lib/traefik/letsencrypt/acme.json
sudo chmod 600 /var/lib/traefik/letsencrypt/acme.json

cp -n "$PLAN_DIR/runtime/environments/vps/infrastructure/edge/traefik/traefik.env" "$PLAN_DIR/runtime/environments/vps/infrastructure/edge/traefik/.env"
cp -n "$PLAN_DIR/runtime/environments/vps/infrastructure/edge/portainer/portainer.env" "$PLAN_DIR/runtime/environments/vps/infrastructure/edge/portainer/.env"
nano "$PLAN_DIR/runtime/environments/vps/infrastructure/edge/traefik/.env"
nano "$PLAN_DIR/runtime/environments/vps/infrastructure/edge/portainer/.env"

docker compose --env-file "$PLAN_DIR/runtime/environments/vps/infrastructure/edge/traefik/.env" -f docker-compose.traefik.yml up -d
docker compose --env-file "$PLAN_DIR/runtime/environments/vps/infrastructure/edge/portainer/.env" -f docker-compose.portainer.yml up -d

# Quick health checks
docker compose --env-file "$PLAN_DIR/runtime/environments/vps/infrastructure/edge/traefik/.env" -f docker-compose.traefik.yml ps
docker compose --env-file "$PLAN_DIR/runtime/environments/vps/infrastructure/edge/portainer/.env" -f docker-compose.portainer.yml ps
docker logs traefik --tail 120 | grep -Ei "provider|acme|error" || true

In runtime/environments/vps/infrastructure/edge/traefik/.env, confirm:

  • LETSENCRYPT_EMAIL
  • TRAEFIK_DASHBOARD_HOST
  • NETBIRD_ALLOWED_CIDRS (keep default 100.64.0.0/10 unless changed in NetBird)
  • ADMIN_BASIC_AUTH (used by Traefik dashboard route)

In runtime/environments/vps/infrastructure/edge/portainer/.env, confirm:

  • PORTAINER_HOST

9) Install NetBird (Existing Traefik Mode)

sudo mkdir -p /opt/netbird
cd /opt/netbird
curl -fsSL https://github.com/netbirdio/netbird/releases/latest/download/getting-started.sh -o getting-started.sh
chmod +x getting-started.sh
sudo ./getting-started.sh

When prompted:

  1. Domain: netbird.perspective-v.com
  2. Reverse proxy: choose [1] Existing Traefik
  3. Auth mode: embedded IdP quickstart
  4. NetBird proxy service: N (disabled for now)

Important integration note:

  • Ensure NetBird web/API traffic is routed through the edge Traefik stack.
  • If the installer outputs additional Existing Traefik instructions, apply them before moving on.
  • If you update runtime files in /opt/netbird later, sync them back to this repository (see step 17).

10) Complete First NetBird Admin Setup

cd /opt/netbird
sudo docker compose ps

Open:

  • https://netbird.perspective-v.com/setup

Create the first admin account.

11) Join an Admin Device to NetBird

  • Install NetBird client on your machine.
  • Sign in and join the network.
  • Confirm the device appears as connected in NetBird dashboard.

12) Detect NetBird Interface and Server VPN IP

NB_IFACE=$(ip -brief addr | awk '/wt0|wg|netbird/{print $1; exit}')
NB_IP=$(ip -4 addr show "$NB_IFACE" | awk '/inet /{print $2}' | cut -d/ -f1)
echo "NB_IFACE=$NB_IFACE"
echo "NB_IP=$NB_IP"

If NB_IFACE is empty, inspect manually:

ip -brief addr

13) Validate Admin Routes Are NetBird-Only

From a NetBird-connected device, verify:

  • Traefik dashboard host works.
  • Portainer host works.

Recommended response checks:

# From NetBird-connected client: expected 401 (before auth) or 200 (after auth)
curl -Ik https://traefik.perspective-v.com

# From NetBird-connected client: expected app login response (typically 200/302), not Traefik 401
curl -Ik https://portainer.perspective-v.com

# From non-NetBird client: expected 403
curl -Ik https://traefik.perspective-v.com

From a non-NetBird network, verify these admin hosts are blocked (or denied by source allowlist).

Do not add broad UFW public allow rules for admin service ports.

13.1) Deploy Shared Service Baseline (Order Required)

Deploy shared middleware first:

cd "$PLAN_DIR/runtime/stacks/infrastructure/platform"
docker network create platform || true
cp -n "$PLAN_DIR/runtime/environments/vps/infrastructure/platform/platform.env" platform.env
nano platform.env
sudo docker compose --env-file platform.env -f docker-compose.platform.yml up -d

In platform.env, keep these values for developer hostname access while preserving internal container hostname usage:

  • REDIS_BIND_IP=0.0.0.0
  • REDIS_HOST_PORT=6379
  • REDIS_INSIGHT_HOST=redis-insight.perspective-v.com
  • REDIS_INSIGHT_BIND_IP=127.0.0.1
  • REDIS_INSIGHT_HOST_PORT=5540

Then deploy update automation from dedicated operations artifacts:

cd "$PLAN_DIR/runtime/stacks/infrastructure/operations"
cp -n "$PLAN_DIR/runtime/environments/vps/infrastructure/operations/operations.env" operations.env
nano operations.env
sudo docker compose --env-file operations.env -f docker-compose.operations.yml up -d
sudo docker logs watchtower --tail 100

Then deploy the Gitea package registry.

The old docker-registry, registry-admin, ProGet, BaGet and Verdaccio stacks under runtime/stacks/infrastructure/registry and .../feeds have been RETIRED and removed. A single Gitea instance at gitea.perspective-v.com (owner org perspective-v) now serves Docker images, npm and NuGet packages. Gitea is deployed with the swarm launcher; see stack-gitea-guide.md for the full procedure:

cd "$PLAN_DIR"
./swarm/scripts/services/service.sh deploy

Gitea package access model:

  • Docker images: gitea.perspective-v.com/perspective-v/<image>:<tag>.
  • npm: https://gitea.perspective-v.com/api/packages/perspective-v/npm/.
  • NuGet v3 index: https://gitea.perspective-v.com/api/packages/perspective-v/nuget/index.json.
  • Auth is a Gitea username + access token used as the password: write:package to push, read:package to pull. docker login targets gitea.perspective-v.com.
  • Use a dedicated CI user (e.g. ci-bot) with a scoped token per pipeline; keep tokens in CI secret stores and rotate independently.

Hard order for baseline rollout:

  • runtime/stacks/infrastructure/platform before runtime/stacks/infrastructure/operations.
  • runtime/stacks/infrastructure/operations before the Gitea package registry.
  • Gitea package registry before runtime/stacks/infrastructure/databases.
  • Keep com.centurylinklabs.watchtower.enable=true only on stateless services.

14) Deploy Databases (MSSQL + PostgreSQL + MySQL + MongoDB)

Before deployment and cutover:

  • In NetBird dashboard, verify VPS peer health and required developer/admin peers are connected.
  • Keep DB/UI hostnames as public A/AAAA records to VPS; do not create a separate NetBird-only DNS zone for this rollout.
cd "$PLAN_DIR/runtime/stacks/infrastructure/databases"
cp -n "$PLAN_DIR/runtime/environments/dev/infrastructure/databases/mssql/mssql.dev.env" mssql.env
cp -n "$PLAN_DIR/runtime/environments/dev/infrastructure/databases/postgres/postgres.dev.env" postgres.env
cp -n "$PLAN_DIR/runtime/environments/dev/infrastructure/databases/mysql/mysql.dev.env" mysql.env
cp -n "$PLAN_DIR/runtime/environments/dev/infrastructure/databases/mongodb/mongodb.dev.env" mongodb.env

Set DB bind IPs for NetBird-restricted host access, then edit strong passwords:

sed -i 's/^MSSQL_BIND_IP=.*/MSSQL_BIND_IP=0.0.0.0/' mssql.env
sed -i 's/^POSTGRES_BIND_IP=.*/POSTGRES_BIND_IP=0.0.0.0/' postgres.env
sed -i 's/^MYSQL_BIND_IP=.*/MYSQL_BIND_IP=0.0.0.0/' mysql.env
sed -i 's/^MONGODB_BIND_IP=.*/MONGODB_BIND_IP=0.0.0.0/' mongodb.env

nano mssql.env
nano postgres.env
nano mysql.env
nano mongodb.env

Start DB services:

cd "$PLAN_DIR/runtime/stacks/infrastructure/databases"
sudo docker compose --env-file mssql.env -f mssql/docker-compose.mssql.yml up -d
sudo docker compose --env-file postgres.env -f postgres/docker-compose.postgres.yml up -d postgres
sudo docker compose --env-file mysql.env -f mysql/docker-compose.mysql.yml up -d mysql
sudo docker compose --env-file mongodb.env -f mongodb/docker-compose.mongodb.yml up -d mongodb

# Optional management UIs (NetBird-only via Traefik)
sudo docker compose --env-file postgres.env -f postgres/docker-compose.postgres.yml --profile admin up -d pgadmin
sudo docker compose --env-file mysql.env -f mysql/docker-compose.mysql.yml --profile admin up -d phpmyadmin
sudo docker compose --env-file mongodb.env -f mongodb/docker-compose.mongodb.yml --profile admin up -d mongo-express

15) Cut Over DB and Redis Firewall to NetBird-Only

cd "$PLAN_DIR/runtime/stacks/infrastructure/netbird"
chmod +x db-port-cutover.sh
NB_IFACE="$NB_IFACE" ./db-port-cutover.sh

16) Validate Final Access Model

From a NetBird-connected machine:

nc -vz "$NB_IP" 1433
nc -vz "$NB_IP" 5432
nc -vz "$NB_IP" 3306
nc -vz "$NB_IP" 27017
nc -vz "$NB_IP" 6379

nc -vz mssql.perspective-v.com 1433
nc -vz pgsql.perspective-v.com 5432
nc -vz mysql.perspective-v.com 3306
nc -vz mongo.perspective-v.com 27017
nc -vz redis.perspective-v.com 6379

# Expected app login response (typically 200/302), not Traefik 401
curl -Ik https://pgadmin.perspective-v.com
curl -Ik https://phpmyadmin.perspective-v.com
curl -Ik https://mongo-express.perspective-v.com

Expected:

  • DB and Redis ports pass over NetBird and fail from non-NetBird networks.
  • Admin dashboard hosts are available only from NetBird-connected clients.
  • Public website/API hosts remain internet-accessible over 80/443 through edge Traefik.

17) Sync Runtime Artifacts Back To Repository

After runtime changes under /opt/netbird, copy updated artifacts into the docs repo:

sudo cp /opt/netbird/docker-compose.yml "$PLAN_DIR/runtime/stacks/infrastructure/netbird/docker-compose.yml"
sudo cp /opt/netbird/config.yaml "$PLAN_DIR/runtime/stacks/infrastructure/netbird/config.yaml"

# netbird.env is intentionally untracked by git (*.env)
sudo cp /opt/netbird/netbird.env "$PLAN_DIR/runtime/stacks/infrastructure/netbird/netbird.env.local"

Then manually keep runtime/environments/vps/infrastructure/netbird/netbird.env aligned with current VPS runtime values.

18) Lightweight Operations Policy

  • Keep always-on services minimal: NetBird, Redis, RabbitMQ, Watchtower, MSSQL, PostgreSQL, MySQL, MongoDB, Portainer, Kener.
  • Keep Platform Redis limited to cache/session/ephemeral coordination only.
  • Skip heavy monitoring stacks for now (Prometheus/Grafana) on 8 GB RAM.
  • Keep DB engines on controlled monthly maintenance updates.

On this page