Skip to content

Installing Crosdesk ​

Crosdesk ships as one Docker image that holds the whole product — the API, the web app and this manual — and needs one PostgreSQL database beside it. This page sets up both with Docker Compose on one server, behind HTTPS.

It is written for whoever runs the installation (the instance operator). Nothing here is needed by the people who use Crosdesk day to day.

What you need ​

A serverLinux on x86-64 (linux/amd64). The image is built for that architecture only; on an ARM machine it runs under emulation, slowly.
DockerDocker Engine with the Compose plugin (docker compose).
A domain and HTTPSCrosdesk refuses to run in production without HTTPS: its session cookie is marked Secure. Put a reverse proxy in front — Caddy, nginx, Traefik or your load balancer — to terminate TLS.
A licence keyFrom Buddhyantra — write to hello@buddhyantra.com. See The licence. You can install without one and add it afterwards.
A mail serverSMTP, or a Mailtrap account where the host blocks SMTP, for invitations and sign-in codes. Can be added later: meanwhile the operator console copies an invitation link to send by hand, and can let workspace admins do the same.
Optional: S3 storageFiles can live on the server's disk (a Docker volume) or in an S3-compatible bucket — AWS S3, MinIO, Cloudflare R2. A bucket is required if you run more than one copy of the app.

The Compose file includes PostgreSQL 16. To use a database you already run instead, see Without Compose; any PostgreSQL 14 or newer works.

1. Get the Compose file ​

Make a directory for the installation and put docker-compose.app.yml from the Crosdesk repository in it — Buddhyantra can also send it, at hello@buddhyantra.com.

sh
mkdir crosdesk && cd crosdesk
# copy docker-compose.app.yml here

It runs two containers: api (the published image, gsmithun4/cros-desk) and postgres, with two volumes — the database, and uploaded files.

2. Write the .env file ​

Beside the Compose file, create .env:

sh
# The address people will use, exactly as typed in a browser.
PUBLIC_URL=https://desk.example.com

# Encrypts the licence, mail password and storage credentials in the database.
# Generate once:  openssl rand -base64 32   — and keep a copy with your backups.
CONFIG_ENCRYPTION_KEY=paste-the-generated-value-here

# The database's own password. Not published outside the Compose network.
POSTGRES_PASSWORD=another-strong-password

# Recommended while the address is public and unclaimed — see First boot.
BOOTSTRAP_TOKEN=a-long-random-value

Runtime settings for the Compose deployment:

VariableRequiredWhat it is
PUBLIC_URLYesThe installation's address. Every link Crosdesk sends — invitations, password resets, sign-in callbacks — starts with it. The app refuses to start without it.
CONFIG_ENCRYPTION_KEYYesEncrypts secret settings at rest. Without it the licence cannot be saved, so nothing works. Changing it later makes the stored secrets unreadable — rotate it with the steps in Upgrading and backups.
POSTGRES_PASSWORDThe database password. Defaults to postgres — change it. POSTGRES_USER and POSTGRES_DB can be set too.
BOOTSTRAP_TOKENAsked for when claiming the operator console, so finding the address first is not enough to take it.
CROSDESK_TAGThe image version to run. Default latest; pin a version for predictable upgrades.
PORTThe port on the server. Default 3000.
TRUST_PROXYHow many proxies sit in front. Default 1, right for one reverse proxy. Without it, every visitor looks like the proxy, and the sign-in rate limit is shared by everyone.
COOKIE_SECURELeave at the default, true.

Everything else — the licence, mail, storage, sign-in providers, session length — is set in the operator console after the first boot, not here. Variables such as LICENSE_KEY, SMTP_HOST or S3_BUCKET are ignored: there is one place for each setting, and it is the console.

3. Start it ​

sh
docker compose -f docker-compose.app.yml up -d
docker compose -f docker-compose.app.yml logs -f api

The first boot applies the database migrations before starting the API. The startup log includes:

▶ node v22.x · PORT=3000 · NODE_ENV=production
▶ Applying migrations...
  55 migration(s) applied, 0 already up to date.
▶ Starting API...

From there the app logs one JSON line per event. The three to look for are No licence is set (expected until step 2 of First boot), Storage: local files in /app/storage (writable) and Crosdesk API listening on :3000.

Later starts reuse the existing schema and accounts. Claim the operator console in the browser if it has not been claimed yet.

If it stops instead, the log says why — for example Refusing to start: PUBLIC_URL must be the address of this installation. Fix it and start again; the schema and workspace made before the refusal are kept and reused. See When it will not start.

4. Put HTTPS in front ​

Point your domain at the server and proxy it to port 3000. With Caddy, which fetches a certificate by itself, the whole configuration is:

desk.example.com {
    reverse_proxy localhost:3000
}

Whatever the proxy, it must pass on the original Host header and X-Forwarded-Proto: https. Crosdesk checks that every form is submitted from its own address, and it learns that address from those two headers; TRUST_PROXY (default 1) tells it to believe them. A proxy that drops them makes every sign-in and every save fail with Cross-origin request rejected. Caddy passes both by default. nginx does not pass Host unless told to:

nginx
server {
    listen 443 ssl;
    server_name desk.example.com;
    # ssl_certificate … ssl_certificate_key …

    client_max_body_size 100m;   # attachments and screen recordings come through here

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host              $host;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
    }
}

Then open PUBLIC_URL in a browser. Two sign-ins exist from here:

  • /login — workspace accounts, once their invitation has been accepted.
  • /super-admin — the operator console, claimed on your first visit. Do this next: First boot and the operator console.

Until a licence is installed, the workspace answers every request with an error that says so — that is expected. The operator console works without one.

Without Compose ​

The image runs anywhere that runs containers. Configure its runtime connection settings:

VariableValue
DATABASE_URLpostgresql://user:password@host:5432/crosdesk. The database is created on first start if the role may create databases; otherwise create it empty first.
PUBLIC_URLThe installation's address.
CONFIG_ENCRYPTION_KEYAs above.
COOKIE_SECUREtrue.
TRUST_PROXYThe number of proxies in front, usually 1 — see Put HTTPS in front.

The image already sets NODE_ENV=production, SERVE_FRONT_END=true and PORT=3000, runs as an unprivileged user, and has a health check on /api/health. Mount a volume at /app/storage, or use S3 storage, so uploads survive the container being replaced.

A Railway deployment is described step by step in DEPLOY-RAILWAY.md in the repository.

When it will not start ​

The reason is the first error in the api log.

The log saysDo this
PUBLIC_URL must be the address of this installationSet PUBLIC_URL in .env.
COOKIE_SECURE must be true in productionRemove any COOKIE_SECURE=false. Crosdesk is only served over HTTPS.
DATABASE_URL is not setSet it, or use the Compose file, which builds it.
Migrations failed. The API has not been started.Usually the database is unreachable or the role lacks rights. The lines above it name the problem.
The deployed image is older than the databaseYou started an older version against a database a newer one has upgraded. Run the newer image. See Upgrading.
CONFIG_ENCRYPTION_KEY is not set (a warning, the app still starts)Set it and restart. Until then the licence and other secrets cannot be saved.

And once it is running:

What you seeDo this
Every sign-in or save fails with Cross-origin request rejectedThe proxy is not passing Host and X-Forwarded-Proto, or TRUST_PROXY is unset. See Put HTTPS in front.
Every workspace screen fails, and /api/health is fineNo valid licence yet — see The licence.

Crosdesk · Buddhyantra LLP