- Shell 100%
Remove proxy_2 dependency on the optional second agent so the HA stack starts without --profile dual-agent. |
||
|---|---|---|
| scripts | ||
| .env.example | ||
| .gitignore | ||
| docker-compose.ha.local.yml | ||
| docker-compose.ha.yml | ||
| docker-compose.local.yml | ||
| docker-compose.yml | ||
| README.md | ||
Kasm Workspaces 1.17.0 — Docker Compose Stack
Production-ready Kasm Workspaces stack based on the official 1.17.0 service images and installer configuration layout.
Architecture
| Layer | Services | Network |
|---|---|---|
| Data | kasm_db, kasm_redis |
kasm-internal only |
| App | kasm_api, kasm_manager, kasm_share, guac/rdp |
kasm-internal only |
| Sessions | kasm_agent (+ optional kasm_agent_2 on separate hosts only) |
kasm-internal + kasm_default_network |
| Entry | kasm_proxy |
kasm-internal + proxy-network |
HA extension (docker-compose.ha.yml): adds a second API/manager/proxy tier for NPM load balancing.
Requirements
- Linux host with Docker Engine (recommended for agents/session containers)
- Docker Compose v2
- External networks:
kasm-internal,kasm_default_network,proxy-network - Node Proxy Manager on
proxy-network(production) - Sufficient CPU/RAM for concurrent sessions (one agent on single-node; scale vertically or add remote agent hosts)
Quick start (Node Proxy Manager)
cp .env.example .env
# Edit passwords and KASM_PUBLIC_HOSTNAME
docker network create kasm-internal 2>/dev/null || true
docker network create kasm_default_network 2>/dev/null || true
# proxy-network should already exist from your NPM stack
./scripts/bootstrap.sh prepare
docker compose up -d db kasm_redis
./scripts/bootstrap.sh init-db
docker compose up -d
NPM configuration
Create a Proxy Host:
| Setting | Value |
|---|---|
| Domain | your Kasm domain |
| Forward Hostname | kasm_proxy |
| Forward Port | 443 |
| Scheme | https |
| Websockets | On |
| Block common exploits | On |
Recommended custom locations / advanced:
- Increase proxy read/send timeout (e.g. 3600s) for streaming sessions
- Ensure the NPM container is attached to
proxy-network
HA with NPM
docker compose -f docker-compose.yml -f docker-compose.ha.yml up -d
Add both upstreams to NPM (custom nginx config or multiple hosts with same domain):
kasm_proxy:443kasm_proxy_2:443
Local access (host ports)
docker compose -f docker-compose.yml -f docker-compose.local.yml up -d
# UI: https://localhost:8443
With HA locally:
docker compose -f docker-compose.yml -f docker-compose.ha.yml -f docker-compose.local.yml -f docker-compose.ha.local.yml up -d
# Primary: https://localhost:8443
# Secondary: https://localhost:8444
Default logins
Set in .env (ADMIN_PASSWORD, USER_PASSWORD). Defaults from bootstrap output:
admin@kasm.localuser@kasm.local
Files
| File | Purpose |
|---|---|
docker-compose.yml |
Core stack (NPM-ready) |
docker-compose.ha.yml |
Second app/proxy tier |
docker-compose.local.yml |
Host port overrides |
docker-compose.ha.local.yml |
HA second proxy port override |
scripts/bootstrap.sh |
Downloads release, certs, config, DB seed |
.env |
Secrets and settings |
Runtime data is generated under ./data/kasm/1.17.0/ (gitignored).
Bootstrap commands
./scripts/bootstrap.sh prepare # Download release + generate runtime config
./scripts/bootstrap.sh init-db # Seed DB (requires db + redis running)
./scripts/bootstrap.sh init-db --fresh # Wipe DB volume + seed (use after rm -rf data/)
./scripts/bootstrap.sh patch-secrets # Re-sync DB/Redis passwords into api config
./scripts/bootstrap.sh all # prepare + db + full stack
If you use compose override files (HA or local ports), export COMPOSE_FILE before bootstrap so init-db stops the same services you started:
export COMPOSE_FILE=docker-compose.yml:docker-compose.local.yml
# or: docker-compose.yml:docker-compose.ha.yml
Scaling concurrent users
- Agents: one agent per central
proxyupstream; add remote agent hosts with their own proxy/nginx path, or scale the host vertically - App tier: use
docker-compose.ha.ymlor add more api/manager/proxy replicas - Resources: size agents per Kasm sizing guide
Troubleshooting
docker compose ps
docker compose logs -f proxy
docker compose logs -f kasm_agent
Agents not registering: verify MANAGER_TOKEN in .env matches seeded value; restart agents.
NPM 502: confirm kasm_proxy is on proxy-network and healthy on port 443.
relation "settings" does not exist / 502 from proxy: database was not seeded. Stop the stack, run ./scripts/bootstrap.sh init-db --fresh, wait for "Database seed verified", then docker compose up -d.
init-db stuck on "pg_isready but not psql": Kasm Postgres requires SSL for remote connections (hostssl in pg_hba.conf). Ensure you have the latest bootstrap.sh (uses PGSSLMODE=require for psql). If the db container shows exited, run docker compose up -d db and retry.
password authentication failed for user "kasmapp": the DB volume was initialized with a different POSTGRES_PASSWORD than your current .env (common after changing .env without wiping the volume). Either run ./scripts/bootstrap.sh init-db --fresh (latest bootstrap removes the db container before deleting the volume), or sync manually:
docker compose exec -T db psql -U kasmapp -d postgres -c "ALTER USER kasmapp WITH PASSWORD 'YOUR_POSTGRES_PASSWORD';"
./scripts/bootstrap.sh init-db
Do not run docker compose up -d (full stack) until init-db completes successfully.
kasm_share Redis invalid password: kasm_share reads redis.redis_password from data/kasm/1.17.0/conf/app/api/api.app.config.yaml. It must match the password the kasm_redis container was started with (REDIS_PASSWORD in .env). Common causes: an older bootstrap wrote the DB password into redis_password, .env was edited after Redis started, or CRLF/trailing spaces in .env (Compose vs bash disagree on the value).
Fix without re-seeding:
./scripts/bootstrap.sh patch-secrets
docker compose restart kasm_share kasm_api kasm_manager
docker compose logs kasm_share --tail 20
Diagnose:
set -a && source .env && set +a
grep -A3 '^redis:' data/kasm/1.17.0/conf/app/api/api.app.config.yaml
docker compose exec kasm_redis redis-cli -a "$REDIS_PASSWORD" ping
Kasm recommends Redis passwords without $, quotes, or other special characters. If you use $ in .env, quote the value (REDIS_PASSWORD='your$password') so Compose and bash expand it the same way.
Seed fails with Permission denied: /tmp/.temp.api.app.config.yaml: tmp/api must be writable by KASM_UID (default 1000). After updating bootstrap, run ./scripts/bootstrap.sh prepare again or:
chown -R 1000:1000 data/kasm/1.17.0/tmp/api
./scripts/bootstrap.sh init-db --fresh
kasm_rdp_gateway crash loop / Permission denied on /tmp/redemption-sesman-sock or rdpproxy/var/pid: RDP services run as KASM_UID (default 1000) with a read-only root filesystem. Bootstrap must own tmp/rdpgw, RDP config dirs, and the nginx TLS key for the HTTPS gateway. After updating bootstrap:
./scripts/bootstrap.sh prepare # re-runs permission fix at end of patch_configs
docker compose restart rdp_gateway kasm_rdp_https_gateway
Or fix ownership manually without re-preparing:
chown -R 1000:1000 data/kasm/1.17.0/tmp/rdpgw \
data/kasm/1.17.0/conf/app/rdp_gateway \
data/kasm/1.17.0/conf/app/rdp_https_gateway \
data/kasm/1.17.0/certs/kasm_nginx.key \
data/kasm/1.17.0/certs/kasm_nginx.crt
chmod 755 data/kasm/1.17.0/tmp/rdpgw/var
docker compose restart rdp_gateway kasm_rdp_https_gateway
Early 502 responses and ENOTFOUND proxy in logs during the first ~30s after docker compose up are normal startup race; components retry until kasm_api and kasm_manager are listening.
"No resource available" / "No Agent slots available" after docker compose restart or up -d: A full stack restart can re-enable a second agent, reset hostnames, or leave agents at 0 cores. Run:
./scripts/bootstrap.sh repair-agents
This re-applies the single-agent upstream, hostname: proxy, manager token sync, disables extra agents in the DB, sets cores_override (default 8, override with KASM_CORES_OVERRIDE in .env), stops kasm_agent_2, and restarts proxy/api/manager/agent. Wait ~30s, then try a session again.
Sessions fail with 401 Unauthorized on agent hello / "Agent did not respond to hello": With a single central proxy, only one agent may be registered with hostname: proxy and listed in upstream_agent.conf. Nginx round-robins /agent/ across upstream members; hello requests are signed per server_id, so a second agent on the same upstream always causes intermittent 401s. Fix on existing installs:
cat > data/kasm/1.17.0/conf/nginx/upstream_agent.conf <<'EOF'
upstream kasm_agent {
server kasm_agent:4444;
}
EOF
AGENT1_ID=$(grep server_id data/kasm/1.17.0/agents/agent-1/agent.app.config.yaml | awk '{print $2}')
docker compose exec -T db psql -U kasmapp -d kasm -c \
"UPDATE servers SET enabled = false WHERE server_type = 'host' AND server_id != '${AGENT1_ID}';"
docker compose stop kasm_agent_2
docker compose restart proxy kasm_api kasm_manager kasm_agent
If hello still returns 401 after the single-agent fix, the manager token in agent configs likely drifted from the database (re-running prepare without init-db). Do not UPDATE settings.token via SQL — Kasm stores it encrypted; writing plain text causes ValueError: block length errors. Instead, read the plain token in Admin → Settings → Manager → Token, set the same value in .env (MANAGER_TOKEN=) and both agent configs (manager.token), then restart kasm_agent and kasm_manager. Or re-seed: ./scripts/bootstrap.sh prepare && ./scripts/bootstrap.sh init-db.
Scale sessions by sizing the host or adding agents on separate hosts (each with its own proxy path), not by load-balancing multiple agent containers on one /agent/ upstream.
Sessions fail to start / "No Agent slots available" / connection refused to kasm_agent:443: In a single-proxy compose stack, agent records in the database must use hostname: proxy, not kasm_agent. The API reaches agents through central nginx at https://proxy:443/agent/.... Fix existing installs without re-seed:
docker compose exec -T db psql -U kasmapp -d kasm -c "UPDATE servers SET hostname = 'proxy' WHERE server_type = 'host';"
docker compose restart kasm_agent kasm_agent_2 kasm_api
Verify from the API container:
docker compose exec kasm_api curl -sk https://proxy:443/agent/api/v1/hello/
If you expose Kasm on a non-443 port (e.g. 8443), set Admin → Zones → default → Proxy Port to that client-facing port.
Sessions fail to start / stuck on "Creating a secure connection": workspace containers are placed on kasm_default_network. Bootstrap creates it automatically; agents, guac, and proxy must be attached (see docker-compose.yml). After updating compose, recreate affected services:
docker network create kasm_default_network 2>/dev/null || true
docker compose up -d kasm_agent kasm_agent_2 kasm_guac proxy rdp_gateway kasm_rdp_https_gateway
Re-running prepare without init-db: regenerates agent/API/manager UUIDs and registration tokens in config files while the database still holds old values. Agents fail to register or sessions behave oddly. Always run init-db after prepare, or use patch-secrets only for password changes.
ARM64 / Apple Silicon: RDP gateway images are amd64-only; bootstrap skips RDP nginx wiring on arm64. Disable or ignore rdp_gateway / kasm_rdp_https_gateway on arm64 hosts.
Password characters: Kasm docs recommend avoiding $, ', and " in Postgres/Redis passwords. Quote .env values that contain $. Use ./scripts/bootstrap.sh patch-secrets after any password change.
Redis host tuning (optional): on Linux production hosts, consider sysctl vm.overcommit_memory=1 and disabling transparent huge pages for Redis stability (warnings in kasm_redis logs).
Notes
- First bootstrap downloads Kasm 1.17.0 release tarball (~cache/kasm_release)
- Self-signed certs are generated for internal/proxy TLS; NPM typically terminates public TLS
- Session containers require Linux +
/var/run/docker.sockon agent hosts