Self-hosting

Docker Compose quickstart

Install Desyn on one machine with the Compose stack that ships in the repository — Convex on Postgres, the web app, the render worker and MinIO storage — step by step.

On this page

The repository ships a complete self-hosting stack in deploy/compose/desyn.selfhost.yml (Compose project desyn-selfhost). This guide takes you from an empty machine to a running installation. It takes 30–60 minutes, most of it waiting for the first image build. You need the requirements in place: Docker Engine with the Compose plugin, and for a server, DNS names and a reverse proxy.

What the stack runs#

ServicePurposeHost port (default)
webThe Desyn app: editor, dashboard, settings, admin console3000
convex-backendDatabase, realtime sync, server functions, HTTP actions, crons3210 (API), 3211 (HTTP actions)
convex-dashboardConvex admin UI127.0.0.1:6791
postgresConvex's persistence, on an internal network only—
minio + minio-initS3-compatible storage; the bucket is created automatically9000 (API), 127.0.0.1:9001 (console)
workerRender worker: exports, thumbnails, AI previews— (Compose network only)

Optional services are behind Compose profiles and start only when you ask for them:

ProfileServicesHost port (default)
marketinglanding and docs (this site)3001, 3002
mailmailpit, an SMTP sink with a web UI for testing email127.0.0.1:8025
ollamaollama, local models for the AI features127.0.0.1:11434
toolsconvex-tools, the Convex CLI with this repository's functions— (used with run --rm)

Published ports bind to DESYN_BIND (default 127.0.0.1), so nothing is reachable from other machines until you put a reverse proxy in front or change the bind address. The dashboard, the MinIO console, Mailpit and Ollama always bind to 127.0.0.1.

Get the source#

Clone the Desyn repository (or unpack a release archive) and enter it. Every command in the self-hosting guides runs from the repository root.

Shell
git clone <repository-url> desyn
cd desyn

Point Compose at the stack#

Shell
export COMPOSE_FILE=deploy/compose/desyn.selfhost.yml

With COMPOSE_FILE set, a plain docker compose … uses the self-hosting stack; the commands in these docs assume it. Add the line to your shell profile, or pass -f deploy/compose/desyn.selfhost.yml to every command instead. Compose reads its variables from deploy/compose/.env, next to the stack file.

Create the stack environment#

Copy the template and fill its four required secrets with fresh random values:

Shell
cp deploy/compose/.env.example deploy/compose/.env
chmod 600 deploy/compose/.env
for name in POSTGRES_PASSWORD INSTANCE_SECRET MINIO_ROOT_PASSWORD RENDER_WORKER_SECRET; do
  sed -i "s|^$name=.*|$name=$(openssl rand -hex 32)|" deploy/compose/.env
done

deploy/compose/.env and deploy/compose/convex.env are ignored by git. Keep both with your backups: INSTANCE_SECRET protects the Convex deployment, and the others are needed to reach your data.

Create the backend configuration#

The server functions read their own configuration from the Convex deployment. Start from the template and fill its secrets, reusing two values from the stack environment:

Shell
cp deploy/compose/convex.selfhost.env.example deploy/compose/convex.env
chmod 600 deploy/compose/convex.env
stack() { grep "^$1=" deploy/compose/.env | cut -d= -f2-; }
put() { sed -i "s|^$1=.*|$1=$2|" deploy/compose/convex.env; }
put BETTER_AUTH_SECRET "$(openssl rand -hex 32)"
put AI_KEY_ENCRYPTION_SECRET "$(openssl rand -hex 32)"
put RENDER_WORKER_SECRET "$(stack RENDER_WORKER_SECRET)"
put AWS_SECRET_ACCESS_KEY "$(stack MINIO_ROOT_PASSWORD)"

The template already selects the self-hosted edition (DESYN_EDITION=selfhost), the bundled worker and MinIO, and the log mail transport. Optionally add OPENROUTER_API_KEY and FAL_AI_API_KEY for AI features, and stock photo keys. The configuration reference explains every variable.

Choose your URLs#

The browser talks to three origins (app, Convex API, Convex HTTP actions) plus the storage endpoint. The public ones are baked into the web image when it is built, so set them before the first build.

Point four DNS names at the server — this guide uses design.example.org, convex.example.org, convex-site.example.org and s3.example.org — and set them in both files:

deploy/compose/.env
DESYN_APP_URL=https://design.example.org
DESYN_SITE_URL=https://design.example.org
DESYN_CONVEX_URL=https://convex.example.org
DESYN_CONVEX_SITE_URL=https://convex-site.example.org
deploy/compose/convex.env
APP_URL=https://design.example.org
SITE_URL=https://design.example.org
S3_ENDPOINT=https://s3.example.org
S3_PUBLIC_BASE_URL=https://s3.example.org/desyn

DESYN_SITE_URL and SITE_URL name the marketing site; without the marketing profile, use the app URL as shown. Keep DESYN_BIND=127.0.0.1 and set up the reverse proxy after the stack is running. MinIO's CORS rule follows DESYN_APP_URL automatically.

Build and start#

Shell
docker compose up -d --build
docker compose ps

The first build compiles the web app and the Rust worker and can take 15–30 minutes (DESYN_BUILD_JOBS in .env sets the parallel Rust jobs). When it finishes, docker compose ps shows every service running and convex-backend and postgres healthy; minio-init exits after creating the bucket.

Generate the admin key#

Shell
docker compose exec convex-backend ./generate_admin_key.sh

Copy the key from the last line of the output into CONVEX_SELF_HOSTED_ADMIN_KEY in deploy/compose/.env; the convex-tools service reads it from there. Treat it like a root password: it grants full control over the deployment. You also use it to log in to the dashboard.

Push the backend configuration#

Shell
grep -E '^[A-Z0-9_]+=.+' deploy/compose/convex.env | docker compose run --rm -T convex-tools env set --force

The first run builds the convex-tools image (a few minutes). The grep passes only variables that have a value, so empty optional keys are not set. Run the same command whenever you change convex.env; changes apply to new function calls immediately.

Deploy the functions#

Shell
docker compose run --rm convex-tools deploy --yes

This type-checks and uploads the schema, functions and scheduled jobs from the image's copy of convex/. Run it again after every upgrade.

Create your account#

Open your app URL and sign up. The first account on a fresh installation becomes the administrator and can open the admin console at /admin.

With the default MAIL_TRANSPORT=log, the verification email is not sent but written to the backend logs: open the dashboard at http://127.0.0.1:6791 (log in with the admin key) and look under Logs. Configure email before you invite anyone.

Check the installation#

  • <DESYN_CONVEX_SITE_URL>/healthz returns {"ok":true,"service":"desyn-convex","edition":"selfhost",…}. A 503 with "ok":false means the backend cannot reach its database.
  • In the editor, upload an image (tests storage and CORS) and export a PNG (tests the worker).
  • Admin → Health in the app shows whether storage, email, the worker and each AI provider are configured.

If something does not work, see troubleshooting.

Optional services#

Start a profile with --profile, then point the backend at it in convex.env and push the configuration again:

Shell
docker compose --profile mail --profile ollama up -d
ProfileBackend configuration
mailMAIL_TRANSPORT=smtp and SMTP_URL=smtp://mailpit:1025; read the messages at http://127.0.0.1:8025
ollamaOLLAMA_BASE_URL=http://ollama:11434, then pull a model: docker compose exec ollama ollama pull qwen3:32b
marketingSet DESYN_SITE_URL and DESYN_DOCS_URL to the public URLs of the landing page and the docs, and SITE_URL to match

Changing URLs later#

The DESYN_*_URL values are compiled into the browser bundles. After changing them, rebuild the images that use them and update the backend:

Shell
docker compose up -d --build web          # add landing docs if you run the marketing profile
grep -E '^[A-Z0-9_]+=.+' deploy/compose/convex.env | docker compose run --rm -T convex-tools env set --force

Ports#

Every host port can be changed in deploy/compose/.env:

VariableDefaultService
DESYN_WEB_PORT3000Web app
DESYN_LANDING_PORT3001Landing page (marketing)
DESYN_DOCS_PORT3002Docs (marketing)
DESYN_CONVEX_PORT3210Convex API
DESYN_CONVEX_SITE_PORT3211Convex HTTP actions
DESYN_DASHBOARD_PORT6791Convex dashboard (always 127.0.0.1)
DESYN_MINIO_PORT9000MinIO S3 API
DESYN_MINIO_CONSOLE_PORT9001MinIO console (always 127.0.0.1)
DESYN_MAILPIT_PORT8025Mailpit UI (mail, always 127.0.0.1)
DESYN_OLLAMA_PORT11434Ollama (ollama, always 127.0.0.1)

Next steps#

Popular pages

QuickstartGet started
Keyboard shortcutsEditor
The agentAI
Docker Compose quickstartSelf-hosting
Configuration referenceSelf-hosting
Agent API (v1)Developers