Skip to main content

Other ways to run it

Other ways to run it​

The sections below describe the image itself, and running it with docker run or your own compose file, for platforms that build from a Dockerfile.

What you need​

NeedDetails
A container hostDocker, Podman, or a platform that builds from a Dockerfile.
Postgres 17The desk keeps everything there, including its job queue (in a pgboss schema).
HTTPS in frontA reverse proxy or tunnel in front of the desk that serves it over https. The desk itself speaks plain http on PORT, and its cookies are only sent over https.
Later, for real useAn email transport (Resend or any SMTP server), S3-compatible object storage, and Cloudflare Turnstile keys. The desk starts without them and lists what is missing.

The image​

Build it from the repository root:

docker build -t itsmengine .
  • Multi-stage on node:22-bookworm-slim. It runs as the node user; the app files are owned by root and read-only to it.
  • tini is PID 1, so SIGTERM reaches Node and shutdown runs cleanly: open requests finish and the job worker stops gracefully.
  • On start the entrypoint runs prisma migrate deploy, then the server. Set MIGRATE_ON_START=false to run migrations as a separate step (see upgrades).
  • It sets NODE_ENV=production and PORT=3000, and listens on all interfaces. Idle memory is about 260 MB (see system requirements).
  • Any other command passes through the entrypoint, so docker run … itsmengine node dist/create-admin.js … works.

Secrets​

Make two secrets once and keep them in your secret store. Each is 32 or more random characters, and they must differ:

openssl rand -base64 48 # SESSION_SECRET
openssl rand -base64 48 # ENCRYPTION_KEY

ENCRYPTION_KEY encrypts stored secrets; losing or changing it has consequences described in backups.

[!WARNING] Never change ENCRYPTION_KEY on a running installation.

Running it with docker run​

  1. Have a Postgres 17 database available.

  2. Put the four variables in a file (never commit it):

    # desk.env
    DATABASE_URL=postgresql://desk:[email protected]:5432/desk
    SESSION_SECRET=<first secret>
    ENCRYPTION_KEY=<second secret>
    PORT=3000
  3. Start the container:

    docker run -d --name desk --restart unless-stopped --env-file desk.env -p 3000:3000 itsmengine
  4. Point your reverse proxy at port 3000.

A portal on its own domain (help.example.com) is served by the same desk: add that host to the proxy too, with a certificate, and keep the Host header (see Portals).

Running it with docker compose​

Prefer the recommended setup in deploy/. Below is a minimal alternative with Postgres in the same project, for when HTTPS and file storage are provided elsewhere. Keep the secrets in a .env file next to it (compose reads it for the ${…} values):

services:
desk:
image: itsmengine # built with `docker build -t itsmengine .`
restart: unless-stopped
environment:
DATABASE_URL: postgresql://desk:${POSTGRES_PASSWORD}@postgres:5432/desk
SESSION_SECRET: ${SESSION_SECRET}
ENCRYPTION_KEY: ${ENCRYPTION_KEY}
ports:
- "3000:3000"
depends_on:
postgres: { condition: service_healthy }

postgres:
image: postgres:17
restart: unless-stopped
environment:
POSTGRES_USER: desk
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: desk
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U desk -d desk"]
interval: 5s
retries: 10

volumes:
pgdata:
docker compose up -d
  • To have compose build the image itself, put the file in the repository root and replace image: with build: ..
  • On a platform that builds from a Dockerfile, create the application from the repository, add a Postgres 17 database and S3-compatible storage, and set the same variables in its environment settings.

Health check​

GET /healthz answers without signing in. The image's HEALTHCHECK calls it every 15 seconds; point your platform's health check and uptime monitor at the same path.

From outside, through your proxy, it says only whether the desk is up: {"status":"ok","database":"ok"}, or 503 with "status":"error".

The details below are only for requests made on the server itself, like the container's own health check or the docker compose exec command in step 6 above. They aren't shown to the internet: they would say which protections aren't set up yet.

AnswerMeaning
200 {"status":"ok","database":"ok","migrations":42}Healthy.
200 with "setup": trueHealthy, in setup mode: no admin yet.
200 with "warnings": ["S3_ENDPOINT", …]Healthy, but some installation settings are missing (below).
200 with "unknownMigrations": […]Healthy, but the database has migrations this image doesn't know: it is older than the database.
503 {"status":"error","database":"unreachable"}The desk can't reach Postgres.
503 {"status":"error","database":"ok","pendingMigrations":[…]}The database is older than the image.

What each warning means is in troubleshooting.

Configuration​

Only DATABASE_URL, SESSION_SECRET, ENCRYPTION_KEY and PORT belong in the environment. Everything else (the product name and public address, trusted proxies, email, file storage, Turnstile and the installation policies) is an installation setting: set in the setup wizard and changed later under Settings → Installation.

Each setting can still come from the environment under the same name; a variable that is set wins and shows as Set by the environment.

In production the desk refuses to start with development secrets or test switches. Missing email, storage or bot protection is only a warning. The full list, with every default, is in configuration.

[!IMPORTANT] Behind a reverse proxy, set Trusted proxies to the proxy's addresses. Otherwise every request seems to come from the proxy, and rate limits and IP allowlists see the wrong address. See troubleshooting.