Installing
ITSM Engine runs as one container, next to Postgres 17 and S3-compatible file storage. This page installs it on one server with Docker Compose and HTTPS, and then describes the image for other ways to run it.
The container holds the API, the agent app and the requesters' pages, embed.js and the background job worker.
Email, bot protection and the rest are set up later in the browser, with the setup wizard.
This guide sets up the Standard deployment: everything on one server. Check the system requirements for the size of server you need.
[!NOTE] Ready-made container images are not published yet, so every way below builds the image from source.
Recommended: Docker Compose with HTTPS
The Docker Compose setup in deploy/ builds ITSM Engine from this repository. It runs it with Postgres, file storage
and Caddy, which gets an HTTPS certificate automatically.
Get the source → Fill in .env → Build and start → Check health → Setup wizard → First admin
Before you start
| Need | Details |
|---|---|
| Server | A Linux server with Docker Engine 25 or later and Docker Compose 2.24 or later. It has been tested with Docker 29 and Compose 5. |
| Size | At least 2 vCPU, 2 GB of memory and 20 GB of disk; more for larger desks (see system requirements). Building the image needs about 2 GB of memory on its own. |
| Domain | A domain whose DNS A/AAAA record points to the server. |
| Ports | Ports 80 and 443 open to the internet. |
Steps
-
Get the source and go to
deploy/:git clone <repository URL> itsmenginecd itsmengine/deployTo install a specific release,
git checkout <tag>before going on. -
Create
.envfrom the example and fill in the secrets:cp .env.example .envfor v in SESSION_SECRET ENCRYPTION_KEY POSTGRES_PASSWORD POSTGRES_ADMIN_PASSWORD S3_SECRET_KEY STORAGE_ADMIN_PASSWORD SETUP_TOKEN; dosed -i.bak "s|^$v=\$|$v=$(openssl rand -hex 32)|" .envdonerm .env.bakThis fills in each secret with a different random value, and
SETUP_TOKEN, which opens the setup wizard. -
Set
DOMAINin.envto your domain. -
Keep a copy of
.envsomewhere safe:ENCRYPTION_KEYcan't be recovered (see the two secrets). -
Build and start:
docker compose up -d --buildThe first build takes a few minutes. Postgres and the file storage start first. The app applies the database migrations and creates its storage bucket; Caddy then gets the certificate for
DOMAIN. -
Check that it is up.
https://<DOMAIN>/healthzshould answer200with{"status":"ok","database":"ok"}. The details (setup mode, missing settings) are only shown on the server itself:docker compose pscurl -s https://<DOMAIN>/healthzdocker compose exec desk node -e "fetch('http://127.0.0.1:3000/healthz').then(r => r.text()).then(console.log)" -
Open the setup wizard. This prints its link:
echo "https://$(sed -n 's/^DOMAIN=//p' .env)/setup?token=$(sed -n 's/^SETUP_TOKEN=//p' .env)" -
In the setup wizard set the installation's name, email (SMTP or Resend), Cloudflare Turnstile and your organization, and create the first admin at the end.
The address, trusted proxies and file storage are already set by
compose.yaml. Anything you skip is under Settings → Installation later.
What runs
| Service | What | Data |
|---|---|---|
desk | ITSM Engine, built from this repository (itsmengine:local). It publishes no port; only Caddy reaches it. | none (stateless) |
postgres | PostgreSQL 17, pinned by digest | volume postgres-data |
storage | S3-compatible file storage (MinIO), pinned by digest | volume storage-data |
storage-init | Runs on each start and exits: creates the bucket, and gives the app its own key (S3_ACCESS_KEY, S3_SECRET_KEY) that reads and writes that bucket only. The storage's admin password, STORAGE_ADMIN_PASSWORD, stays with the storage. | none |
caddy | HTTPS on ports 80 and 443, with automatic Let's Encrypt certificates | volumes caddy-data, caddy-config |
Every service restarts by itself. The data lives in named Docker volumes, which survive docker compose down
and re-creating containers. Back them up with backup.sh.
[!CAUTION] Only
docker compose down -vdeletes the volumes, and with them the data.
External file storage
To use your provider's S3-compatible storage instead of the bundled one, set S3_ENDPOINT, S3_REGION,
S3_BUCKET, S3_ACCESS_KEY and S3_SECRET_KEY in .env. The storage service then sits unused, and
storage-init does nothing. Give the desk a key limited to its bucket there too.
An existing reverse proxy
If the server already has a proxy (nginx, Traefik, a platform's router), you can remove the caddy service:
- Publish the app's port to the proxy only, for example
ports: ["127.0.0.1:3000:3000"]ondesk. - Forward the
Hostheader andX-Forwarded-For. - Set
TRUSTED_PROXIESto the proxy's address. - Let live updates through as they come: don't buffer or compress
/api/v1/stream(in nginx,proxy_buffering off;for that path), and allow it to stay open (a read timeout over 30 seconds; the desk writes to it every 25). If the proxy holds it back, the desk still works, but sidebar counts and colleagues on a case update more slowly.
The app speaks plain HTTP and its cookies need HTTPS in front of it.
Portals on their own domain
-
Add a block per domain to
Caddyfile(there is a commented example). -
Point the domain's DNS to the server.
-
Reload Caddy:
docker compose exec caddy caddy reload --config /etc/caddy/Caddyfile
Email
Set it up in the browser, or set MAIL_TRANSPORT, SMTP_URL or RESEND_API_KEY, and MAIL_DEFAULT_FROM, in
.env. Brands send from their own sender address once its domain is verified with your email service. See
configuration.
In this section
- Other ways to run it: The image, secrets, docker run, your own compose file, the health check and configuration.
- First start and the first admin: What happens on first start, and creating the first admin.