Skip to content

Install with Docker Compose

Run the controller container against an external PostgreSQL and put nginx (or any TLS-terminating reverse proxy) in front of it. The compose file runs only the controller — you bring the database.

  • A host with Docker and Docker Compose.
  • PostgreSQL 16+, reachable from the host (managed service or a separate server — for a quick trial you can add a postgres:16 service to the same compose file).
  • A domain name and a TLS certificate (Let’s Encrypt below).
  • Paid plans only: your per-organization license token and public key from the Tessera portal. Community needs no license — just skip the license lines below.

Generate the encryption and JWT keys once and keep them in a .env file next to the compose file (running Community? drop the license line):

Terminal window
cat > .env <<EOF
TESSERA_ENC_KEY=$(openssl rand -hex 32)
TESSERA_JWT_SECRET=$(openssl rand -hex 32)
TESSERA_LICENSE=<license token>
EOF
chmod 600 .env
docker-compose.yml
services:
controller:
image: registry.tessera.company/tessera/controller:v1.0.0
restart: unless-stopped
ports:
- "127.0.0.1:8443:8443" # nginx terminates public TLS and proxies here
environment:
# External PostgreSQL — the compose file does not run a database
TESSERA_DATABASE_URL: "postgres://tessera:PASSWORD@db.example.com:5432/tessera?sslmode=require"
TESSERA_PUBLIC_URL: "https://controller.example.com"
TESSERA_HTTP_ADDR: ":8443"
TESSERA_HOST_KEY: "/data/ssh_host_ed25519_key" # generated once, kept on the volume
TESSERA_RECORDINGS_DIR: "/data/recordings"
TESSERA_ENC_KEY: "${TESSERA_ENC_KEY}"
TESSERA_JWT_SECRET: "${TESSERA_JWT_SECRET}"
TESSERA_LICENSE: "${TESSERA_LICENSE}"
volumes:
- controller_data:/data # SSH host key + session recordings — must persist
volumes:
controller_data:

The image is pulled from Tessera’s public registry, so no login is needed. Pin the tag to a released version (see Installation options); the schema migrates automatically on start.

The controller serves HTTPS on :8443 (self-signed internally), and every proxied protocol rides WebSocket over that port — so nginx must forward the Upgrade header and use long timeouts. Put the map in the http block once:

map $http_upgrade $connection_upgrade { default upgrade; '' close; }

Then the server block (Let’s Encrypt terminating TLS):

server { listen 80; server_name controller.example.com; return 301 https://$host$request_uri; }
server {
listen 443 ssl;
server_name controller.example.com;
ssl_certificate /etc/letsencrypt/live/controller.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/controller.example.com/privkey.pem;
proxy_ssl_verify off; # controller uses a self-signed cert internally
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 3600s; # long-lived SSH / DB / RDP sessions
proxy_send_timeout 3600s;
proxy_buffering off; # stream interactive terminals / RDP
client_max_body_size 0; # large transfers
location /metrics { deny all; } # keep metrics internal
location / { proxy_pass https://127.0.0.1:8443; }
}

TESSERA_PUBLIC_URL must match the public name so OIDC redirects and notification links resolve.

Terminal window
docker compose --env-file .env up -d
docker compose logs -f controller # watch it come up and migrate

Open https://controller.example.com. On a fresh database the controller bootstraps a default administrator — sign in as admin / admin and change the password immediately. Then follow the initial setup walkthrough.

Terminal window
docker compose pull controller
docker compose --env-file .env up -d controller

The container restarts and active sessions drop — upgrade on purpose. Back up your PostgreSQL database, the .env file (especially TESSERA_ENC_KEY), and the controller_data volume (SSH host key) — see Backups & upgrades. Configuration lists every setting and Licensing & activation covers how the license is applied.