DocsSelf-hosting › Operations

Self-hosting operations (updates, backup, env, monitoring)

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.

Audience: self-hosting (Paid/Founder) · Docker Compose · basic Linux knowledge helpful

The stack at a glance

The-Y runs as a Docker Compose stack. The services:

ServicePurpose
appthe web app (Next.js) — binds internally on port 3000
workerbackground jobs (email fetch, reminders, sweeps, automation scheduler)
postgresdatabase (all tenant data)
redisjob queue (BullMQ)
miniofile/media storage (attachments, recordings) — S3-compatible
caddyreverse proxy with automatic HTTPS
watchtoweroptional auto-updater (nightly image pull)
optional: asterisk, coturn, piperphone system, TURN (WebRTC), neural voice announcements

Updates

Two ways — both pull the new images from Docker Hub:

cd ~/the-y-crm
docker compose pull app worker
docker compose up -d app worker
Database migrations run automatically on app start (the container runs migrate before start) — no separate step needed.

Backup & restore

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!).

Backup

# 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

# 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"
The product also has a per-tenant snapshot export/import (Settings → Backup) — handy for migrations and as an application-level backup. The DB+MinIO backup above is the full infrastructure backup.
The encryption key is critical. Mailbox/SIP/API/channel tokens are stored encrypted with 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).

Environment variables (reference)

The key values in your .env (template: .env.self-host.example). Critical ones in bold.

VariablePurpose
APP_ENCRYPTION_KEYFixed 32-byte hex key (openssl rand -hex 32) for field encryption. Never change.
DATABASE_URLPostgres superuser connection (migration/scripts)
APP_DATABASE_URLapp connection as non-superuser crm_app (enforces row-level security = tenant isolation)
BASE_DOMAINyour domain (e.g. crm.yourcompany.com) — used e.g. for the softphone (WSS)
TENANT_MODEself-host: single (one tenant, no external signup)
LICENSE_KEYyour founder/licence key; validated online (14-day offline tolerance)
CONTROL_PLANE_URLlicence check endpoint (default: crm.the-y.at)
S3_ENDPOINT / S3_BUCKETMinIO binding (minio:9000 / crm-media)
MINIO_ROOT_USER / _PASSWORDMinIO credentials (generate)
PLATFORM_SMTP_* / PLATFORM_MAIL_FROMoptional system mailer (password reset/invitations) without your own mailbox
CRM_IMAGE/_WORKER_IMAGE/CRM_TAGDocker Hub images + tag (default :latest)
telephony: AMI_SECRET etc.only with the phone system enabled

Reverse proxy & TLS

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

Monitoring & health

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.

Licence

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.

Troubleshooting

After a re-setup, mailbox/WhatsApp/telephony are "broken"

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.

Mail doesn't go out (timeout)

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.

Instance is in read-only mode

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.

A container won't start

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.

What's next