Skip to main content

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.

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​

NeedDetails
ServerA 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.
SizeAt 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.
DomainA domain whose DNS A/AAAA record points to the server.
PortsPorts 80 and 443 open to the internet.

Steps​

  1. Get the source and go to deploy/:

    git clone <repository URL> itsmengine
    cd itsmengine/deploy

    To install a specific release, git checkout <tag> before going on.

  2. Create .env from the example and fill in the secrets:

    cp .env.example .env
    for v in SESSION_SECRET ENCRYPTION_KEY POSTGRES_PASSWORD POSTGRES_ADMIN_PASSWORD S3_SECRET_KEY STORAGE_ADMIN_PASSWORD SETUP_TOKEN; do
    sed -i.bak "s|^$v=\$|$v=$(openssl rand -hex 32)|" .env
    done
    rm .env.bak

    This fills in each secret with a different random value, and SETUP_TOKEN, which opens the setup wizard.

  3. Set DOMAIN in .env to your domain.

  4. Keep a copy of .env somewhere safe: ENCRYPTION_KEY can't be recovered (see the two secrets).

  5. Build and start:

    docker compose up -d --build

    The 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.

  6. Check that it is up. https://<DOMAIN>/healthz should answer 200 with {"status":"ok","database":"ok"}. The details (setup mode, missing settings) are only shown on the server itself:

    docker compose ps
    curl -s https://<DOMAIN>/healthz
    docker compose exec desk node -e "fetch('http://127.0.0.1:3000/healthz').then(r => r.text()).then(console.log)"
  7. 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)"
  8. 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​

ServiceWhatData
deskITSM Engine, built from this repository (itsmengine:local). It publishes no port; only Caddy reaches it.none (stateless)
postgresPostgreSQL 17, pinned by digestvolume postgres-data
storageS3-compatible file storage (MinIO), pinned by digestvolume storage-data
storage-initRuns 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
caddyHTTPS on ports 80 and 443, with automatic Let's Encrypt certificatesvolumes 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 -v deletes 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:

  1. Publish the app's port to the proxy only, for example ports: ["127.0.0.1:3000:3000"] on desk.
  2. Forward the Host header and X-Forwarded-For.
  3. Set TRUSTED_PROXIES to the proxy's address.
  4. 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​

  1. Add a block per domain to Caddyfile (there is a commented example).

  2. Point the domain's DNS to the server.

  3. 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​