Public operator documentation Last updated

Install Worklist in your own Docker environment.

Self-hosted Worklist is distributed as a Docker image and configured with a signed offline license file. This page shows the real deployment model before you buy: what you run, what you mount, and what your operators maintain.

The quickstart uses the supplied image digest, a self-hosted environment file, a mounted license, and secret-backed database credentials prepared in the steps below.

Self-hosted quickstart

docker compose

docker compose \  -f docker-compose.self-hosted.yml \  --env-file .env.self-hosted up -d

Start the stack with the self-hosted Compose template and environment file.

Distribution

A Worklist self-hosted Docker image plus a Compose template.

License

A signed offline license file mounted read-only into the container.

Database

PostgreSQL plus bundled MinIO object storage in the supplied Compose stack.

Security posture

Same-origin app/API, TLS reverse proxy, and no public marketing site inside the image.

Before you buy

What you need to run it.

The self-hosted package is intentionally boring infrastructure: one Worklist application container, PostgreSQL, MinIO for encrypted attachments, Docker secrets, and your reverse proxy. The marketing site is not shipped in the self-hosted image; the image serves the workspace app and API.

Runtime

Docker Engine with Docker Compose, or an equivalent container platform.

Network

A public HTTPS origin behind a reverse proxy such as Caddy, nginx, Traefik, or your ingress controller.

Storage

Persistent PostgreSQL and MinIO volumes; external S3-compatible storage can replace MinIO.

Secrets

Stable JWT secrets, OPAQUE server setup, database credentials, and the Worklist license file.

Operator access

An administrator who can back up PostgreSQL, rotate license files, and restart the application.

Install

From license bundle to running stack.

Worklist supplies the image reference, Compose template, environment example, signed license file, and one-time bootstrap token. Operators pin the image, mount the license as a Docker secret, generate runtime secrets once, and start the stack.

Step 1

Prepare the environment file.

Copy the supplied example, load it for the current shell, and fail early if the licensed image reference is missing.

Prepare .env.self-hosted

host shell

cp .env.self-hosted.example .env.self-hosted# Set WORKLIST_IMAGE to the tag@digest reference from your license bundle.set -a. ./.env.self-hostedset +a: "${WORKLIST_IMAGE:?set WORKLIST_IMAGE in .env.self-hosted to the supplied tag@digest image reference}"

You should now have a local .env.self-hosted file with WORKLIST_IMAGE set to the supplied tag@digest reference.

Step 2

Install the offline license.

Place the signed license where the Compose stack expects it. On Linux, make it group-readable by the Worklist container group.

Install license file

host shell

install -m 640 /path/from/license-bundle/worklist-license.json ./worklist-license.jsonif [ "$(uname -s)" = "Linux" ]; then  sudo chown :10001 worklist-license.jsonfitest -r worklist-license.json

The license file should be readable on the host and ready to mount read-only as a Docker secret.

Step 3

Generate runtime secrets once.

Create database, object-storage, JWT, and OPAQUE setup secrets. Keep these files backed up with PostgreSQL and the license.

Generate secret files

host shell

mkdir -p secretsopenssl rand -hex 24 > secrets/postgres_passwordopenssl rand -hex 32 > secrets/minio_root_passwordopenssl rand -hex 20 > secrets/r2_secret_access_keyprintf 'postgres://worklist:%s@postgres:5432/worklist\n' "$(cat secrets/postgres_password)" > secrets/database_urlopenssl rand -base64 48 > secrets/jwt_access_secretopenssl rand -base64 48 > secrets/jwt_refresh_secretdocker run --rm --entrypoint /usr/local/bin/generate_opaque_setup "$WORKLIST_IMAGE" > secrets/opaque_server_setup_b64chmod 600 secrets/postgres_password secrets/minio_root_password secrets/r2_secret_access_keychmod 640 secrets/database_url secrets/jwt_access_secret secrets/jwt_refresh_secret secrets/opaque_server_setup_b64if [ "$(uname -s)" = "Linux" ]; then  sudo chown :10001 secrets/database_url secrets/jwt_access_secret secrets/jwt_refresh_secret secrets/opaque_server_setup_b64fifor file in secrets/postgres_password secrets/minio_root_password secrets/r2_secret_access_key secrets/database_url secrets/jwt_access_secret secrets/jwt_refresh_secret secrets/opaque_server_setup_b64; do  test -r "$file"done

The secrets directory should contain the credentials and OPAQUE setup needed by the Worklist container.

Step 4

Start and verify the stack.

Bring up the Compose stack with the self-hosted environment file, then confirm Worklist, PostgreSQL, and MinIO are running.

Start Docker Compose

docker compose

docker compose -f docker-compose.self-hosted.yml --env-file .env.self-hosted up -ddocker compose -f docker-compose.self-hosted.yml --env-file .env.self-hosted ps

The stack should report running containers before you put your HTTPS proxy in front of it.

The Worklist container reads files as UID/GID 10001. On Linux hosts, the Worklist secrets are 0640 and group-readable by that container group; the PostgreSQL, MinIO, and object-storage secret files stay 0600 on the host and are mounted through Docker secrets. The generated Worklist object-storage secret is a 40-character MinIO-compatible secret key.

1

Verify the image.

Use the tag and digest supplied with the license bundle. Avoid mutable floating tags in production.

2

Preserve secrets.

Keep the OPAQUE setup value, JWT secrets, database URL, and license file backed up with the database.

3

Put TLS in front.

Expose the app through HTTPS and configure email/invite URL templates to the same external origin.

Configure

The settings that matter.

Secret-valued settings should use the matching *_FILE variables. Startup rejects ambiguous sources when both a direct value and a file value are set for the same secret.

WORKLIST_IMAGE

The immutable self-hosted image reference supplied with your license bundle. Pin a tag and digest in production.

BILLING_MODE

Set to self_hosted. Self-hosted mode is loaded at startup and is not a runtime toggle.

SELF_HOSTED_LICENSE_PATH

Path inside the container to the signed license file. The Compose template uses /run/secrets/worklist_license.

SELF_HOSTED_ALLOW_PUBLIC_SIGNUP

Keep false for internet-reachable instances unless a separate access-control layer protects registration.

DATABASE_URL_FILE

Path to the PostgreSQL connection URL secret. Do not set both DATABASE_URL and DATABASE_URL_FILE.

AUTH_OPAQUE_SERVER_SETUP_B64_FILE

Generate once and preserve exactly as emitted by generate_opaque_setup; losing it can strand existing accounts.

JWT_ACCESS_SECRET_FILE and JWT_REFRESH_SECRET_FILE

Stable production secrets generated separately with at least 32 characters each.

EMAIL_* URL templates

The example uses localhost for a local boot; set production values to your external HTTPS origin.

R2_BUCKET, R2_ENDPOINT, and R2_PUBLIC_ENDPOINT

Bucket name plus internal and browser-facing S3-compatible endpoints. The default stack points them at bundled MinIO.

MINIO_CORS_ALLOWED_ORIGIN

Local boot allows the localhost app origin. Production should set this to the Worklist app origin, using only scheme, host, and optional port.

Bootstrap and seats

First account is deliberate.

Bootstrap admin

The first registration requires the one-time setup token supplied with the license bundle when public signup is disabled. Once the first owner is created, bootstrap is recorded in the database and the setup token cannot be reused.

Seat accounting

The signed license carries an instance-wide seat limit and expiry date. Registered users and pending seat invitations count toward the limit, so new registrations and invitations stop when capacity is reached.

Keep SELF_HOSTED_ALLOW_PUBLIC_SIGNUP=false for internet-reachable deployments unless your proxy, VPN, identity-aware access layer, or network boundary already restricts who can reach registration.

After license expiry, existing data remains readable, but billable writes, registrations, and new seat invitations are rejected until a renewed license file is mounted and the service is restarted.

When email delivery is disabled, seat invite creation returns a one-time join token in the authenticated API response. Keep request and response body logging disabled for invite-management routes.

Maintain

Runbook-level basics.

Backups

Back up PostgreSQL, MinIO data, the license file, .env.self-hosted, and the secrets directory together. If you use external S3 storage, back up that bucket instead.

Renewals

Replace worklist-license.json with the renewed file and restart the Worklist container. The license is read at startup; there is no hot reload path.

Upgrades

Pull the new image reference, keep the digest pinned, and restart through Compose. The API runs bundled database migrations before accepting traffic.

Transport

Terminate HTTPS at your proxy, preserve Host and scheme headers, and keep the default loopback bind unless a trusted firewall or proxy is in front.

Email

Self-hosted mail can be disabled for manual invite links or sent through Postmark; SMTP support is a tracked product follow-up.

Attachments

The default Compose stack starts MinIO, creates R2_BUCKET, creates a bucket-scoped Worklist access key, and applies CORS for presigned browser uploads. Set MINIO_CORS_ALLOWED_ORIGIN to the Worklist HTTPS origin in production. Rotating the object-storage secret or changing R2_PUBLIC_ENDPOINT can invalidate active presigned URLs. External R2 or S3 works too, but operators should remove the MinIO services and minio-init dependency from their Compose copy.

Renew a license.

Renew license

docker compose

install -m 640 /path/to/renewed/worklist-license.json ./worklist-license.jsondocker compose -f docker-compose.self-hosted.yml --env-file .env.self-hosted restart worklist

The replacement license is read on restart; there is no hot reload path.

Proxy example.

Caddy reverse proxy

Caddyfile

worklist.example.com {    reverse_proxy 127.0.0.1:3000} s3.worklist.example.com {    reverse_proxy 127.0.0.1:9000}

Route the Worklist app and S3-compatible attachment origin through HTTPS.

Proxy environment

.env.self-hosted

R2_PUBLIC_ENDPOINT=https://s3.worklist.example.comMINIO_CORS_ALLOWED_ORIGIN=https://worklist.example.com

Point browser upload URLs and MinIO CORS at the public HTTPS origins.

Trust boundary

Transparent limits are part of the product.

Data stays encrypted.

Worklist keeps the same end-to-end encrypted workspace model in self-hosted mode.

Licenses are offline.

The runtime verifies the signed license locally. There is no online revocation check in the self-hosted app.

Operators are trusted.

License state guards rollback and misconfiguration; it is not meant to defeat a privileged administrator who controls the host and database.

Want to evaluate the bundle?

We can walk through the image, license terms, bootstrap flow, and maintenance expectations with your operator before purchase.