This manual is for founders who self-host The-Y CRM. It covers day-to-day operations: the Docker stack, updates, backup & restore, the key environment variables, reverse proxy/TLS, monitoring and the licence. For first install & server sizing, see Installation and server requirements.
The-Y runs as a Docker Compose stack. The services:
| Service | Purpose |
|---|---|
app | the web app (Next.js) — binds internally on port 3000 |
worker | background jobs (email fetch, reminders, sweeps, automation scheduler) |
postgres | database (all tenant data) |
redis | job queue (BullMQ) |
minio | file/media storage (attachments, recordings) — S3-compatible |
caddy | reverse proxy with automatic HTTPS |
watchtower | optional auto-updater (nightly image pull) |
optional: asterisk, coturn, piper | phone system, TURN (WebRTC), neural voice announcements |
Two ways — both pull the new images from Docker Hub:
watchtower service in the self-host bundle pulls new :latest images for app/worker nightly and restarts them. A banner in the app reports new versions (update check against crm.the-y.at).cd ~/the-y-crm docker compose pull app worker docker compose up -d app worker
migrate before start) — no separate step needed.Regularly back up three things: the Postgres database, the MinIO volume (files/media) and your .env (contains, among others, the encryption key — without it, encrypted secrets are lost!).
# 1) database dump cd ~/the-y-crm docker compose exec -T postgres pg_dump -U crm crm | gzip > db-$(date +%F).sql.gz # 2) MinIO volume (files/media) docker run --rm -v the-y-crm_minio-data:/data -v "$PWD":/backup alpine \ tar czf /backup/minio-$(date +%F).tgz -C /data . # 3) back up .env (contains APP_ENCRYPTION_KEY!) cp .env env-$(date +%F).bak
Store the three files in a separate location (different server/storage). Automate it with cron.
# restore the DB gunzip -c db-YYYY-MM-DD.sql.gz | docker compose exec -T postgres psql -U crm crm # restore the MinIO volume (stop the stack first) docker run --rm -v the-y-crm_minio-data:/data -v "$PWD":/backup alpine \ sh -c "rm -rf /data/* && tar xzf /backup/minio-YYYY-MM-DD.tgz -C /data"
APP_ENCRYPTION_KEY (AES-256). If you change or lose this key, all existing secrets become undecryptable. Set it once, fixed, and back it up (part of your .env backup).The key values in your .env (template: .env.self-host.example). Critical ones in bold.
| Variable | Purpose |
|---|---|
APP_ENCRYPTION_KEY | Fixed 32-byte hex key (openssl rand -hex 32) for field encryption. Never change. |
DATABASE_URL | Postgres superuser connection (migration/scripts) |
APP_DATABASE_URL | app connection as non-superuser crm_app (enforces row-level security = tenant isolation) |
BASE_DOMAIN | your domain (e.g. crm.yourcompany.com) — used e.g. for the softphone (WSS) |
TENANT_MODE | self-host: single (one tenant, no external signup) |
LICENSE_KEY | your founder/licence key; validated online (14-day offline tolerance) |
CONTROL_PLANE_URL | licence check endpoint (default: crm.the-y.at) |
S3_ENDPOINT / S3_BUCKET | MinIO binding (minio:9000 / crm-media) |
MINIO_ROOT_USER / _PASSWORD | MinIO credentials (generate) |
PLATFORM_SMTP_* / PLATFORM_MAIL_FROM | optional system mailer (password reset/invitations) without your own mailbox |
CRM_IMAGE/_WORKER_IMAGE/CRM_TAG | Docker Hub images + tag (default :latest) |
telephony: AMI_SECRET etc. | only with the phone system enabled |
The bundle ships Caddy with automatic HTTPS (Let's Encrypt) — you just point your domain at the server via DNS. If you already run nginx/Apache in front, proxy to the app (internal port 3000) and terminate TLS there instead. Make sure WebSocket connections are passed through (softphone/realtime).
The endpoint /api/public/health returns a simple health status (HTTP 200 = healthy). Wire it into your monitoring (uptime check). Check container status with docker compose ps, logs with docker compose logs -f app worker.
The LICENSE_KEY is validated online against the control plane periodically; on outage a 14-day offline tolerance applies. If the licence is invalid/expired, the instance enters a gentle read-only state (no hard lockout, no data deletion). The feature set is identical to the hosted version.
Almost always the APP_ENCRYPTION_KEY changed (e.g. regenerated), which makes existing encrypted secrets unreadable. Restore the original key from your .env backup. If it's gone for good, the external secrets (tokens/passwords) must be re-entered.
Many cloud providers (Hetzner, netcup, DigitalOcean, AWS/GCP/Azure) block outbound SMTP ports (25/465/587) by default as spam protection. Have the ports unblocked at the provider (support ticket) or use an HTTP-API mail send. Details: server requirements §5b.
The licence check failed (invalid/expired key, or no successful online check for over 14 days). Check LICENSE_KEY and the reachability of the CONTROL_PLANE_URL.
docker compose logs app shows the cause. Common: wrong DB credentials, a missing APP_ENCRYPTION_KEY, or a busy port. After .env changes, docker compose up -d --force-recreate app worker.