NetStacksNetStacks

Installing the Controller

Enterprise

Install the NetStacks Controller with Docker Compose: prerequisites, the interactive installer, first login, trusting the HTTPS certificate, and post-install steps.

Overview

The NetStacks Controller is a self-hosted server that you run with Docker Compose. One install gives you the admin console, the browser-based web terminal at /terminal/, the REST API, and integrated network discovery — all served over HTTPS on a single port, 3000.

NetStacks provides a release package named netstacks-controller-<version>.tar.gz and access to the private container registry (ghcr.io/netstacks/netstacks-controller). The package contains an interactive installer that writes your configuration, pulls the images, starts the stack and prints the initial admin password.

What Gets Installed

The release package contains:

deploy/docker-compose.yml       # single-instance stack
deploy/docker-compose.ha.yml    # high-availability stack (multiple API instances)
deploy/.env.example             # configuration template
deploy/install.sh               # interactive installer
deploy/upgrade.sh               # upgrade helper (backup + version bump + health check)
deploy/crawler/deployment.yml   # discovery (crawler) configuration

The stack runs these containers on a Docker network named netstacks_net:

ServicePurpose
admin-uinginx serving the admin console and web terminal, and reverse-proxying the API. This is the only container with a published port: host 3000 → container 443.
apiThe Controller API (HTTPS, internal port 3000). Runs database migrations on start.
dbBundled PostgreSQL 16 with pgvector (optional — you can use your own PostgreSQL instead).
valkeyValkey 8 — live-session registry, session sharing and HA coordination.
crawler-backend, crawler-workerIntegrated network discovery. Internal only; the API proxies it at /api/crawler/*.

The API also starts containers on demand through the Docker socket: one jumpbox container per user (for the Local Terminal and device scripts), and one container per enabled plugin. For this reason the api container mounts /var/run/docker.sock.

Data lives in three Docker volumes:

  • pgdata — the PostgreSQL database (bundled DB only)
  • valkey_data — Valkey persistence
  • netstacks_data — mounted at /data: the internal TLS CA and certificates, the cached license public key, and plugin storage

Prerequisites

  • A Linux host with Docker Engine (daemon running, and your user allowed to use it) and the Docker Compose v2 plugin. These are the installer's only preflight checks.
  • An x86-64 (amd64) host — the Controller images are published for amd64 only.
  • openssl (to generate secrets) and curl (to wait for health checks).
  • A GitHub username and a personal access token with the read:packages scope that has been granted access to the NetStacks Controller images.
  • Outbound internet access from the host to ghcr.io (image pulls) and to www.netstacks.net (license activation and periodic license validation).
Sizing

NetStacks does not publish fixed CPU, memory or disk requirements. Size the host for PostgreSQL plus the number of concurrent users and jumpbox containers you expect, and keep room for database backups.

Air-gapped networks

The Controller is not designed for fully air-gapped installs: images are pulled from GHCR and the license is activated and revalidated online.

Run the Installer

  1. Copy the release package to the host, extract it and start the installer:

    tar xzf netstacks-controller-<version>.tar.gz
    cd deploy
    ./install.sh
  2. Registry login. If the host is not already logged in to ghcr.io, the installer runs docker login ghcr.io and asks for your GitHub username and token. For unattended installs, set GHCR_USER and GHCR_TOKEN in the environment first.

  3. Answer the prompts (press Enter to accept the default shown in brackets):

    PromptWhat to enter
    Deployment mode1 Single instance (default) or 2 High Availability, which also asks for the number of API instances (default 2). See High Availability.
    Controller versionlatest, or pin a specific release such as 0.0.20.
    External PostgreSQLN uses the bundled database (a password is generated for you). y asks for host, port, user, password and database name and checks that the server is reachable.
    Vault master keyAuto-generated 64-character hex key that encrypts stored credentials. Accept the generated value and keep a copy somewhere safe.
    Service token secretAuto-generated; used to sign plugin service tokens.
    TLS SANsThe hostname(s)/IP(s) users will type to reach the Controller (default: the host's FQDN).
    Docker group GIDDetected from /var/run/docker.sock; needed for jumpboxes and plugins.
    Custom jumpbox imageN uses ghcr.io/netstacks/netstacks-controller/jumpbox:latest. You can change the image later under Settings → Jumpboxes.
    Log levelinfo (default), or debug / warn.
  4. The installer writes .env (file mode 600), pulls the images, runs docker compose up -d, and waits for PostgreSQL, Valkey and https://localhost:3000/health to respond.

  5. When it finishes, it prints a summary like this:

           Installation Complete
    
      Admin UI:   https://controller.example.com:3000
      API:        https://controller.example.com:3000/api
    
      Username:   admin
      Password:   ****************
    
      Save these credentials — the password is only shown on first boot.
Back up the vault master key

VAULT_MASTER_KEY in .env encrypts every stored secret. It cannot be changed after credentials are stored, and it is not part of database backups. If you lose it, stored credentials cannot be decrypted. Copy .env to a secure location now.

Re-running the installer

If you run install.sh again and choose to overwrite the existing .env while using the bundled database, the installer removes the old database volume and starts fresh. To change the version only, answer N to the overwrite question (the installer then updates just NETSTACKS_VERSION) or use upgrade.sh.

First Login

  1. Browse to https://<your-host>:3000. Until you trust the Controller's certificate (next section) the browser shows a certificate warning.
  2. Sign in with username admin and the password the installer printed. If you missed it, read it from the API log:
    docker compose logs api | grep "Password:"
  3. Change the password: open Administration → Users, edit admin, enter a new password and click Save Changes. Passwords must be at least 12 characters and not a common password.
NetStacks Enterprise Controller sign-in page with Username and Password fields

The built-in admin user holds two roles: admin (all permissions in the Default Organization) and platform-admin (manage organizations). See Roles & Permissions.

HTTPS Certificate

The Controller always serves HTTPS. On first start it creates an internal certificate authority named NetStacks (valid 10 years, stored in the netstacks_data volume) and uses it to sign the server certificate. The CA survives upgrades and restarts, so clients only need to trust it once.

Make sure the certificate covers your hostname

The server certificate always includes localhost, 127.0.0.1, the container's own IPs, and the names listed in the Subject Alternative Names setting. After installing:

  1. Open Settings → TLS / SSL. The Current certificate panel lists the SANs that are in use.
  2. Under Self-signed certificate options, edit Sans and enter every hostname and IP users connect with, comma-separated (for example controller.example.com,10.0.0.50), then Save.
  3. Restart the API and the web front end so a new certificate is issued and served:
    docker compose restart api admin-ui
Settings TLS / SSL category showing the current certificate details and Subject Alternative Names

Trust the NetStacks CA

Download the CA certificate (no login required) and add it to your operating system or browser trust store, or distribute it with your device-management tooling:

curl -k -o netstacks-ca.pem https://controller.example.com:3000/api/tls/ca-certificate

NetStacks Terminal desktop clients use the same CA when they connect to the Controller in enterprise mode.

Post-Install Checklist

  1. Change the admin password (see above) and store .env securely.
  2. Activate your license under Administration → License. NetStacks Terminal users cannot sign in to the Controller until a license is active. See Licensing.
  3. Generate a new JWT signing secret. In Settings → Authentication, edit JWT Secret, paste a long random value (for example the output of openssl rand -base64 48) and confirm Rotate secret. Then restart the API (docker compose restart api). Everyone is signed out once.
  4. Set the certificate SANs and distribute the CA (see above).
  5. Set Settings → General → Base Url and Settings → HA / Clustering → Advertise Address to the external URL, for example https://controller.example.com:3000. The advertise address is used to build session-share links.
  6. Create roles and users (Users, Roles & Permissions), and if you host several teams or customers, organizations.
  7. Check Administration → Platform Health: all containers should be up.
  8. Download a first backup (Upgrades & Backups).

Using an External PostgreSQL

To use your own PostgreSQL 16 server instead of the bundled container:

  • The database must have the pgvector extension available.
  • The database user needs superuser or CREATEROLE so the API can create the crawler role and schema used by discovery. Otherwise discovery stays disabled until a DBA creates them.
  • Set DATABASE_URL (and CRAWLER_DATABASE_URL for discovery) in .env and leave COMPOSE_PROFILES unset. The installer does this for you when you answer y to the external PostgreSQL question.

Without the bundled DB, start the services explicitly:

docker compose up -d valkey api admin-ui crawler-backend crawler-worker

.env Reference

The installer writes these values. Edit .env and run docker compose up -d to apply changes.

VariablePurpose
NETSTACKS_VERSIONController image tag (latest or a pinned version).
COMPOSE_PROFILES=bundled-dbStarts the bundled PostgreSQL container. Omit for an external database.
POSTGRES_PASSWORDBundled database password. Only used when the database volume is first created.
DATABASE_URLExternal PostgreSQL connection string.
VAULT_MASTER_KEYRequired. 64 hex characters (openssl rand -hex 32). Encrypts stored secrets.
SERVICE_TOKEN_SECRETSigns plugin service tokens.
DOCKER_GIDGroup ID of /var/run/docker.sock (stat -c '%g' /var/run/docker.sock).
JUMPBOX_IMAGEFirst-boot default for the jumpbox image; afterwards managed in Settings → Jumpboxes.
RUST_LOGLog level (default info).
CRAWLER_VERSION, CRAWLER_DB_PASSWORD, CRAWLER_DATABASE_URLDiscovery image tag, crawler DB role password (default crawler), and crawler connection string for external PostgreSQL.

Valkey is preconfigured in the compose files (VALKEY_URL=redis://valkey:6379). To use Valkey Sentinel instead, set VALKEY_SENTINEL_URLS and VALKEY_SENTINEL_MASTER (default mymaster) on the api service.

Resetting the Admin Password

If you lose the admin password:

  1. In docker-compose.yml, uncomment ADMIN_PASSWORD_RESET: "true" in the api service environment.
  2. Restart and read the new password:
    docker compose up -d api
    docker compose logs api | grep "Password:"
  3. Comment the variable out again and run docker compose up -d api.

Troubleshooting

  • Image pull denied — your GitHub token needs read:packages and access to the NetStacks images. Run docker login ghcr.io again.
  • "API did not become healthy" — check docker compose logs api. A missing or malformed VAULT_MASTER_KEY or an unreachable database stops the API from starting.
  • Discovery pages are empty or report the crawler unreachable — with an external database, confirm the DB user can create roles or that the crawler role/schema exist, and that CRAWLER_DATABASE_URL is set.
  • Jumpboxes or plugins fail to start — check that DOCKER_GID matches the group of /var/run/docker.sock.
  • Browser warns the certificate name doesn't match — add the hostname to the TLS SANs and restart api and admin-ui.
  • Liveness and readiness endpoints (no login): GET /health and GET /ready (reports database and Valkey status).