Skip to main content

Production Deployment

For a long-running server (a VPS behind a reverse proxy), use the production Compose file that ships with Hrida Terminal: docker-compose-terminal-prod.yaml. It comes with health checks, log rotation, resource limits and hardened security defaults, and reads all per-server settings from a single .env file.


What you get​

Health checkCurls /health every 30 seconds; Compose reports the container as healthy
Log rotationContainer logs capped at 3 × 10 MB
Resource limits2 CPUs / 4 GB RAM by default, with soft reservations
Fork-bomb guardProcess count capped at 2048 (TERMINAL_PIDS_LIMIT)
Clean shutdown30-second grace period so terminal and notebook sessions exit cleanly
Hardeningno-new-privileges on by default (see multi-user mode)
Shared networkJoins the external hridaai-network, so it can reach the Hrida.ai stack and its Postgres

1. Put the files on the server​

bash
sudo mkdir -p /opt/hrida-terminal && cd /opt/hrida-terminal
# copy docker-compose-terminal-prod.yaml and .env.example here
cp .env.example .env
The file must be named .env

Compose only reads a file named exactly .env, in the folder you run docker compose from, to fill in ${VAR} values in the Compose file. A file called terminal.env or similar is silently ignored for these values.

2. Create the shared network​

Skip this if the Hrida.ai stack already created it:

bash
docker network create hridaai-network 2>/dev/null || true

3. Fill in .env​

Only set what differs on your server. Everything else falls back to the defaults in the Compose file.

.env
HRIDA_TERMINAL_API_KEY=<openssl rand -base64 48>
HRIDA_TERMINAL_TAG=v1.0.0
HRIDA_TERMINAL_CORS_ALLOWED_ORIGINS=https://terminal.example.com
TERMINAL_BIND_ADDR=127.0.0.1
TERMINAL_HOST_PORT=8086
HRIDA_TERMINAL_INFO=Production terminal (shared sandbox)
VariableWhat to set
HRIDA_TERMINAL_API_KEYA strong key. Leave blank to auto-generate one and read it from the logs
HRIDA_TERMINAL_TAGPin a release instead of latest
HRIDA_TERMINAL_CORS_ALLOWED_ORIGINSYour real front-end origin(s), comma-separated
TERMINAL_BIND_ADDR127.0.0.1 if your reverse proxy runs on the same host
TERMINAL_HOST_PORTPort published on the host (default 8086)
HRIDA_TERMINAL_INFOA short description of this environment, shown to the AI

Lock the file down:

bash
chmod 600 .env

Check that Compose actually reads it:

bash
docker compose -f docker-compose-terminal-prod.yaml config | grep -E 'API_KEY|CORS|published'

4. Start and verify​

bash
docker compose -f docker-compose-terminal-prod.yaml pull
docker compose -f docker-compose-terminal-prod.yaml up -d
docker compose -f docker-compose-terminal-prod.yaml ps      # running + healthy
curl -fsS http://127.0.0.1:8086/health

5. Reverse proxy​

TLS ends at your proxy; the container speaks plain HTTP. Terminal and notebook sessions stream over WebSockets, so forward the upgrade headers and turn buffering off:

nginx
location / {
    proxy_pass http://127.0.0.1:8086;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_buffering off;
    proxy_read_timeout 3600s;
}

If the proxy runs as a container, attach it to hridaai-network and route to hrida-terminal:8000 instead.


Multi-user mode in production​

Multi-user mode creates a Linux account for each Hrida.ai user with sudo useradd. The production Compose file enables Docker's no-new-privileges hardening by default, and that blocks sudo. To use multi-user mode, turn it off in .env:

.env
HRIDA_TERMINAL_MULTI_USER=true
HRIDA_TERMINAL_NO_NEW_PRIVILEGES=false

Then recreate the container. A restart is not enough, because security options are fixed when the container is created:

bash
docker compose -f docker-compose-terminal-prod.yaml up -d --force-recreate
docker inspect hrida-terminal --format '{{.HostConfig.SecurityOpt}}'
# expected: [no-new-privileges:false]

If multi-user mode is on and no-new-privileges is still active, Hrida Terminal refuses to start and logs:

RuntimeError: HRIDA_TERMINAL_MULTI_USER requires sudo, but the container runs
with no-new-privileges, which blocks sudo from elevating. ...

See Troubleshooting if the value doesn't take effect.


Resource limits​

VariableDefaultPurpose
TERMINAL_CPU_LIMIT / TERMINAL_MEM_LIMIT2 / 4GHard ceilings
TERMINAL_CPU_RESERVATION / TERMINAL_MEM_RESERVATION0.5 / 512MSoft reservations
TERMINAL_PIDS_LIMIT2048Max processes. Raise for many parallel sessions

Shared Postgres access​

To let code running in the terminal reach the Hrida.ai Postgres database, uncomment the DB_HOST, DB_PORT, DB_NAME, DB_USER and DB_PASS lines in the Compose file and set the values in .env. They are passed into terminal sessions as environment variables; the terminal server itself does not use them.


Updating​

bash
# change HRIDA_TERMINAL_TAG (or any value) in .env, then:
docker compose -f docker-compose-terminal-prod.yaml pull
docker compose -f docker-compose-terminal-prod.yaml up -d
Update the Compose file too

When you upgrade, copy the latest docker-compose-terminal-prod.yaml to the server as well, not just the image. New settings (such as HRIDA_TERMINAL_NO_NEW_PRIVILEGES) only work if the Compose file on the server references them.

Back up the hrida-home volume (session files and process logs) together with .env.


OpenShift​

An OpenShift-compatible image is built from Dockerfile.openshift. It runs as an arbitrary non-root UID (group 0, as OpenShift requires) and has no sudo, so some features are not available:

FeatureOpenShift image
Command execution, files, terminals, notebooks✔
HRIDA_TERMINAL_API_KEY_FILE (secrets)✔
Multi-user mode✘ The container exits with an error if enabled
Egress filtering (HRIDA_TERMINAL_ALLOWED_DOMAINS)✘ Ignored with a warning
Startup package installs (HRIDA_TERMINAL_PACKAGES, _PIP_PACKAGES, _NPM_PACKAGES)✘ Ignored with a warning

For per-user isolation on OpenShift, run one instance per user or team.


Troubleshooting​

SymptomFix
Values in .env have no effectThe file isn't named exactly .env, or you ran Compose from another folder. Check with docker compose ... config.
.env changed but the container didn'tRun up -d --force-recreate. docker restart and the restart: unless-stopped policy reuse the old settings.
Multi-user: startup error about no-new-privilegesSet HRIDA_TERMINAL_NO_NEW_PRIVILEGES=false and recreate. If docker compose config still shows no-new-privileges:true, the Compose file on the server is an older copy with true hard-coded. Replace it with the current version, or change the line to - no-new-privileges:false.
network hridaai-network ... could not be founddocker network create hridaai-network
Container exits: both API_KEY and API_KEY_FILE are setSet only one of them
All requests return 401Wrong or missing bearer token. If auto-generated, read it from docker compose logs
Browser blocked by CORSAdd the exact front-end origin to HRIDA_TERMINAL_CORS_ALLOWED_ORIGINS
Terminal stream disconnects behind the proxyForward Upgrade/Connection, set proxy_buffering off and a long proxy_read_timeout
Sessions fail with resource temporarily unavailableRaise TERMINAL_PIDS_LIMIT
Container OOMKilledRaise TERMINAL_MEM_LIMIT
env_file / required parse errorUpgrade to Docker Compose v2.24 or newer

Hrida.ai is proprietary software of Zlabs Innovation. See the license for terms. © 2026 Zlabs Innovation.