Not rented. Built. / tested guide

Self-Hosted Chat Automation: The Complete Build

ChatbotX (open source) on your own hardware. Facebook Messenger, Instagram, Telegram, WhatsApp, webchat... running on a machine you own, with AI replies from a model you control. No per-contact pricing. No message caps.

This is the exact build, including every error hit along the way and the fix for each. Total time if you follow this guide: ~45 minutes. Total time discovering everything in this guide: one long evening. I am publishing it because I do not want you paying me every month for a system running on your machine.

I didn’t build this to become your next monthly bill.

I built this with open-source software because your customer messages and your AI should not depend on me or on any single software company. Especially not the closed platforms that have dominated this space while charging exorbitant monthly fees. Follow the guide or hire me to install it. Either way, you own the server, domain, login details, data, and the steps to run it.

The install is a handoff, not a subscription. You may still pay your server, domain, model, or other providers. You do not pay me every month to keep using the system I set up.

What you need

  • A Linux box or VM with Docker. 16GB RAM minimum. The stack runs three Node services plus Postgres, Redis, and object storage. 8GB will not survive it.
  • Coolify installed on that machine (or plain docker compose; the guide assumes Coolify).
  • A domain on Cloudflare, with a Cloudflare Tunnel to the machine. Zero ports get opened on your router... all ingress goes through the tunnel.
  • A Meta developer account (free) for the Facebook/Instagram channels.
  • Optional but recommended: your own OpenAI-compatible inference endpoint for AI replies. Any endpoint works. The platform is bring-your-own-key.
Supplemental readiness check The guide below records the working build. Before starting it, install and prove each dependency here so a missing tool cannot masquerade as a ChatbotX failure.
Required / host

Linux host + memory

Set up: provision a Linux box or VM with sudo access and at least 16GB RAM for this stack. A local model needs additional RAM or VRAM.

Verify
uname -s && free -h
Required / runtime

Docker Engine + Compose

Set up: use Docker’s official distro instructions and install the Compose plugin.

Verify
sudo docker run hello-world && docker compose version
Required / control plane

Coolify

Set up: confirm the installer URL in the current official installation guide, download and inspect the script, then run it and finish onboarding.

Install
curl -fsSLo coolify-install.sh https://cdn.coollabs.io/coolify/install.sh && less coolify-install.sh && sudo bash coolify-install.sh

Verify: the dashboard opens and the connected server reports healthy.

Required / ingress

Cloudflare domain + tunnel

Set up: add the domain, install cloudflared, then create and run a locally managed tunnel.

Verify
cloudflared tunnel ingress validate

The connector should show connected before you add the three routes in Step 2.

Required / secrets

OpenSSL

Set up: install it through your host package manager. On Debian or Ubuntu:

sudo apt-get update && sudo apt-get install -y openssl
Verify
openssl version
Required / recovery flow

SSH access

Set up: enable your distro’s OpenSSH server and configure a key or working login before you depend on the MailHog tunnel.

Verify from another machine
ssh you@your-box
Conditional / Meta channels

Meta developer account

Set up: complete developer registration and create an app.

Verify: the app dashboard opens, an App ID and secret are available, and you have admin access to the Page or professional IG account you will connect.

Optional / AI replies

OpenAI-compatible endpoint

Set up: install or provision the endpoint you control, select a model, and keep its key private.

Verify: send one real /v1/chat/completions request from the host or container network and receive a controlled response before Step 7.

Architecture in one paragraph

Nine containers: builder (the web app), worker (background jobs), realtime (websockets), postgres (TimescaleDB + pgvector), redis, filesystem (RustFS, S3-compatible storage), filesystem-init (one-shot bucket creator), mailhog (catches all email; this matters, so keep reading), adminer (DB admin UI). Three of them need public hostnames through the tunnel; the rest stay internal.

Browser + channels
Cloudflare Tunnel
Meta / Telegram / webchat
Three public routes
builder · web app · :3123
realtime · websockets · :1999
filesystem · assets · :9004
Private stack + local ops
worker · background jobs
postgres · TimescaleDB + pgvector
redis · job + state cache
filesystem-init · bucket bootstrap
mailhog · loopback :8025
adminer · loopback :8081

Text equivalent: browser and messaging traffic crosses the Cloudflare Tunnel into builder, realtime, or filesystem. The worker and data services communicate only on the Compose network; MailHog and Adminer are reached through loopback-only host bindings.

Nine steps. Every fix included.

Mark each step complete as you go. Your progress stays in this browser. The guide explains the working system because ownership should include knowing how it works.

Host shellFoundation

Step 1: Pick your ports

The upstream compose publishes ports you don't need public and will collide with anything else on your box (Postgres 5432, Redis 6379, MinIO-style 9000/9001, Adminer 8080). Check what's taken:

Bash / host
sudo ss -ltnp | grep -E ':(1999|3123|8025|8081|9004|9005)\b' || echo "Chosen ports are free"

This build uses loopback-only host bindings for 3123 (builder), 1999 (realtime), 9004/9005 (RustFS), 8025 (MailHog), and 8081 (Adminer). Cloudflare Tunnel can reach those local ports without exposing them on the public network. Adjust to your host, but keep the 127.0.0.1 bindings.

Checkpoint Confirm your chosen host ports are not already assigned, and verify your provider firewall does not expose them, before pasting the Compose file.
CloudflareFoundation

Step 2: Cloudflare Tunnel routes

Three published application routes on your tunnel:

Public browser-facing routes
HostnameService
chatbotx.yourdomain.comhttp://localhost:3123
chatbotx-rt.yourdomain.comhttp://localhost:1999
chatbotx-s3.yourdomain.comhttp://localhost:9004

Realtime and storage need their own hostnames because browsers hit them directly (websocket + media assets).

Bootstrap gate: before the first deploy, put chatbotx.yourdomain.com behind a temporary Cloudflare Access policy that allows only your email. Keep it protected while the seeded demo login exists. Do not make the builder hostname public until Step 5 confirms the demo password has been rotated and the fixed login no longer works.
Checkpoint Save all three hostname routes, confirm the tunnel connector is connected, and confirm the temporary Access policy blocks an unauthenticated browser. Test application responses after Step 5, when the services are running.
CoolifyStack

Step 3: The compose file

New Coolify resource → Docker Compose Empty → paste. This is the upstream compose with every fix already applied (each fix is explained in the Troubleshooting section):

YAML / complete compose
services:
  postgres:
    networks:
      - main
    image: 'timescale/timescaledb-ha:pg18.4-ts2.27.2-all'
    restart: unless-stopped
    volumes:
      - 'db-data:/home/postgres/pgdata'
    environment:
      - POSTGRES_DB=chatbotx
      - POSTGRES_USER=chatbotx
      - 'POSTGRES_PASSWORD=${POSTGRES_PASSWORD}'
    healthcheck:
      test:
        - CMD-SHELL
        - 'pg_isready -d chatbotx -U chatbotx'
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 20s
  adminer:
    networks:
      - main
    image: 'adminer:5.4.2'
    restart: unless-stopped
    depends_on:
      postgres:
        condition: service_healthy
    ports:
      - '127.0.0.1:8081:8080'
  mailhog:
    networks:
      - main
    image: 'mailhog/mailhog:v1.0.1'
    restart: unless-stopped
    ports:
      - '127.0.0.1:8025:8025'
  filesystem:
    networks:
      - main
    image: 'rustfs/rustfs:1.0.0-beta.8'
    restart: unless-stopped
    command: 'server /data --console-address ":9001"'
    ports:
      - '127.0.0.1:9004:9000'
      - '127.0.0.1:9005:9001'
    environment:
      RUSTFS_ACCESS_KEY: '${RUSTFS_ACCESS_KEY}'
      RUSTFS_SECRET_KEY: '${RUSTFS_SECRET_KEY}'
    volumes:
      - 'filesystem-data:/data'
    healthcheck:
      test:
        - CMD
        - sh
        - '-c'
        - 'curl -f http://127.0.0.1:9000/health'
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s
  filesystem-init:
    networks:
      - main
    image: 'rustfs/rc:v0.1.22'
    depends_on:
      filesystem:
        condition: service_healthy
    environment:
      RUSTFS_ACCESS_KEY: '${RUSTFS_ACCESS_KEY}'
      RUSTFS_SECRET_KEY: '${RUSTFS_SECRET_KEY}'
    entrypoint: "/bin/sh -c \"rc alias set local http://filesystem:9000 $${RUSTFS_ACCESS_KEY} $${RUSTFS_SECRET_KEY}; rc mb local/chatbotx --ignore-existing; touch /tmp/empty && rc cp /tmp/empty local/chatbotx/public/; rc anonymous set public local/chatbotx/public;\"\n"
  redis:
    networks:
      - main
    image: 'redis:8.8.0-alpine'
    restart: unless-stopped
    volumes:
      - 'redis-data:/data'
    healthcheck:
      test:
        - CMD
        - redis-cli
        - ping
      interval: 10s
      timeout: 5s
      retries: 5
  builder:
    networks:
      - main
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
      filesystem:
        condition: service_healthy
    image: 'ghcr.io/chatbotxio/chatbotx-builder:main'
    platform: linux/amd64
    restart: unless-stopped
    environment:
      BETTER_AUTH_SECRET: '${BETTER_AUTH_SECRET}'
      BETTER_AUTH_URL: '${WEB_URL}'
      DATABASE_URL: 'postgresql://chatbotx:${POSTGRES_PASSWORD}@postgres:5432/chatbotx?schema=public'
      ENCRYPTION_KEY: '${ENCRYPTION_KEY}'
      PLATFORM_ADMIN_EMAIL: '${PLATFORM_ADMIN_EMAIL}'
      REALTIME_BROADCAST_SECRET: '${REALTIME_BROADCAST_SECRET}'
      RUN_DB_MIGRATE: 'true'
      RUN_DB_SEED: 'false'
      SMTP_FROM: no-reply@yourdomain.com
      NEXT_PUBLIC_ASSET_URL: '${ASSET_URL}'
      NEXT_PUBLIC_BUILDER_URL: '${WEB_URL}'
      NEXT_PUBLIC_EDITION: community
      NEXT_PUBLIC_ENVIRONMENT: dev
      NEXT_PUBLIC_SMTP_FROM: no-reply@yourdomain.com
      NEXT_PUBLIC_REALTIME_URL: '${REALTIME_URL}'
      NODE_OPTIONS: '--max_old_space_size=2048'
      REALTIME_AUTH_URL: 'http://builder:3000'
      REALTIME_API_KEY: '${REALTIME_API_KEY}'
      REDIS_URL: 'redis://redis:6379'
      S3_ACCESS_KEY_ID: '${RUSTFS_ACCESS_KEY}'
      S3_BUCKET: chatbotx
      S3_ENPOINT: 'http://filesystem:9000'
      S3_REGION: us-east-1
      S3_SECRET_ACCESS_KEY: '${RUSTFS_SECRET_KEY}'
      SCHEDULER_BUCKET_RANGE: 0-255
      SMTP_SERVER: 'smtp://username:password@mailhog:1025'
    ports:
      - '127.0.0.1:3123:3000'
    healthcheck:
      test:
        - CMD
        - curl
        - '-f'
        - 'http://localhost:3000/api/health'
      interval: 30s
      timeout: 10s
      retries: 3
  worker:
    networks:
      - main
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
      filesystem:
        condition: service_healthy
    image: 'ghcr.io/chatbotxio/chatbotx-worker:main'
    platform: linux/amd64
    restart: unless-stopped
    command:
      - worker
      - all
    environment:
      BETTER_AUTH_SECRET: '${BETTER_AUTH_SECRET}'
      BETTER_AUTH_URL: '${WEB_URL}'
      DATABASE_URL: 'postgresql://chatbotx:${POSTGRES_PASSWORD}@postgres:5432/chatbotx?schema=public'
      ENCRYPTION_KEY: '${ENCRYPTION_KEY}'
      PLATFORM_ADMIN_EMAIL: '${PLATFORM_ADMIN_EMAIL}'
      REALTIME_BROADCAST_SECRET: '${REALTIME_BROADCAST_SECRET}'
      RUN_DB_MIGRATE: 'false'
      RUN_DB_SEED: 'false'
      SMTP_FROM: no-reply@yourdomain.com
      NEXT_PUBLIC_ASSET_URL: '${ASSET_URL}'
      NEXT_PUBLIC_BUILDER_URL: '${WEB_URL}'
      NEXT_PUBLIC_EDITION: community
      NEXT_PUBLIC_ENVIRONMENT: dev
      NEXT_PUBLIC_SMTP_FROM: no-reply@yourdomain.com
      NEXT_PUBLIC_REALTIME_URL: '${REALTIME_URL}'
      NODE_OPTIONS: '--max_old_space_size=2048'
      REALTIME_AUTH_URL: 'http://builder:3000'
      REALTIME_API_KEY: '${REALTIME_API_KEY}'
      REDIS_URL: 'redis://redis:6379'
      S3_ACCESS_KEY_ID: '${RUSTFS_ACCESS_KEY}'
      S3_BUCKET: chatbotx
      S3_ENPOINT: 'http://filesystem:9000'
      S3_REGION: us-east-1
      S3_SECRET_ACCESS_KEY: '${RUSTFS_SECRET_KEY}'
      SCHEDULER_BUCKET_RANGE: 0-255
      SMTP_SERVER: 'smtp://username:password@mailhog:1025'
  realtime:
    networks:
      - main
    image: 'ghcr.io/chatbotxio/chatbotx-realtime:main'
    platform: linux/amd64
    restart: unless-stopped
    entrypoint: "bash -c \"cd /app/apps/realtime; echo \\\"REALTIME_BROADCAST_SECRET=$${REALTIME_BROADCAST_SECRET}\\\" > .env; HOSTNAME=0.0.0.0 PORT=1999 pnpm dlx partykit dev;\"\n"
    environment:
      REALTIME_BROADCAST_SECRET: '${REALTIME_BROADCAST_SECRET}'
      NEXT_PUBLIC_BUILDER_URL: '${WEB_URL}'
      NEXT_PUBLIC_REALTIME_URL: '${REALTIME_URL}'
      REALTIME_AUTH_URL: 'http://builder:3000'
      REALTIME_API_KEY: '${REALTIME_API_KEY}'
    ports:
      - '127.0.0.1:1999:1999'
networks:
  main:
    driver: bridge
volumes:
  db-data: null
  redis-data: null
  filesystem-data: null

First deploy only: set RUN_DB_SEED: 'true' on the builder service. The seed creates the DEMO workspace and the default login. Flip it back to 'false' after first successful boot.

Supplemental production note The tested service set is preserved, with host exposure restricted to loopback and internal calls using Docker service DNS. The :main application tags are mutable; for repeatable production rebuilds, record the tested image digests. On an ARM host, platform: linux/amd64 may use emulation. Keep S3_ENPOINT exactly as supplied unless ChatbotX upstream confirms a different variable name.
CoolifyStack

Step 4: Environment variables

Coolify → Environment Variables tab. Generate secrets on the box:

Bash / host
openssl rand -hex 24   # POSTGRES_PASSWORD
openssl rand -hex 32   # ENCRYPTION_KEY
openssl rand -hex 32   # REALTIME_BROADCAST_SECRET
openssl rand -base64 32  # BETTER_AUTH_SECRET
openssl rand -base64 32  # REALTIME_API_KEY
openssl rand -base64 32  # RUSTFS_SECRET_KEY
Coolify environment values
VariableValue
WEB_URLhttps://chatbotx.yourdomain.com
REALTIME_URLhttps://chatbotx-rt.yourdomain.com
ASSET_URLhttps://chatbotx-s3.yourdomain.com/chatbotx/
PLATFORM_ADMIN_EMAILthe email YOU will sign up with
RUSTFS_ACCESS_KEYchatbotx
everything elsegenerated above
The rule that saves you an hour: any secret that lives inside a URL (the Postgres password rides inside DATABASE_URL) must be hex, never base64. Base64 contains / and =, which break URL parsing, and the failure mode is a cryptic "Invalid environment variables" crash loop.
Coolify + browserBootstrap

Step 5: Deploy and first login

Deploy. First boot pulls ~3GB of images and runs migrations. Give it a few minutes. filesystem-init will show perpetual "Restarting" in Coolify; that's cosmetic (it exits 0, Coolify relaunches it). Delete the service from the compose once uploads work.

While the builder hostname is still protected by your temporary Cloudflare Access policy, log in with the seeded account: demo@example.com / Demo@1234.

Immediately do these three things:
  1. Rotate the demo password. There's no in-app password change. Use the forgot-password flow: log out → /auth/forgot-password → enter demo@example.com → the reset email lands in MailHog, not a real inbox. SSH tunnel to read it: ssh -L 8025:localhost:8025 you@your-box, then open http://localhost:8025. In a private browser, authenticate through Cloudflare Access first, then verify the original fixed password no longer works.
  2. Create your own account at /auth/sign-up (verification email: MailHog again), then log back in as demo → workspace Settings → Admins → generate an invitation → open the invitation URL as your new account. Community edition grants full admin on invite.
  3. Fix the workspace timezone (the seed sets Asia/Saigon) and rename the DEMO workspace.

Then visit /admin as your account. This is the super-admin panel, unlocked when PLATFORM_ADMIN_EMAIL matches your email. Remove the temporary Cloudflare Access policy only after the original demo login fails, your own administrator works, and RUN_DB_SEED is back to 'false'.

Checkpoint Confirm uploads work, your new admin can open /admin, the DEMO workspace is renamed, and RUN_DB_SEED is back to 'false'.
Meta + ChatbotXChannels

Step 6: The Meta app (Messenger + Instagram)

One developer app at developers.facebook.com covers Messenger, Instagram, and WhatsApp. For pages and IG accounts you admin, no App Review is needed. Instagram requires the account be a Professional account (Creator or Business, which is a free switch in IG settings). Personal Facebook profiles cannot be automated at all; that's a Meta API limit, not a ChatbotX one.

  1. In ChatbotX /admin → Platform Credentials → enter your Meta App ID + Secret per platform. Set a webhook verify token using any random string, such as openssl rand -hex 16. Copy the webhook URL it shows you.
  2. In the Meta app → Webhooks → select object Page (that's Messenger) → paste callback URL + verify token → Verify and Save → subscribe feed (this is the field that carries comments). Don't panic if messages/messaging_postbacks aren't in the list. The newer dashboard hides messaging fields from this screen, and ChatbotX subscribes them programmatically when you connect each page (a Reconnect on the page in ChatbotX re-fires that call).
  3. Flip the object dropdown to Instagram → paste the Instagram callback URL + token → subscribe to messages, comments.
    Verify what actually got saved: sidebar → Activity log. Every subscription change is listed with its object. This is how you catch feed landing on user webhooks instead of page webhooks (the dropdown makes that mistake easy, and the symptom is FB comments silently never arriving while IG works fine).
  4. Whitelist the OAuth redirect URIs (or connecting a channel throws "Invalid redirect_uri"). Instagram Business Login settings and Facebook Login for Business keep separate whitelists:
    • https://chatbotx.yourdomain.com/integrations/instagram/callback
    • https://chatbotx.yourdomain.com/integrations/instagram/facebook/callback
    • https://chatbotx.yourdomain.com/integrations/messenger/callback
  5. Flip the app from Development to Live mode (needs a privacy policy URL + icon). In Development mode, webhooks only fire for app admins and testers. It works for you and silently drops everyone else. Read the toggle carefully: the header labels both positions ("App Mode: Development ⟷ Live"), so a filled toggle next to "Live" means you're already Live. Clicking it opens a confirmation to switch back to Development, and the way back to Live can require a data-access review that takes days. If the dialog asks "switch to development mode?", Cancel.
  6. ChatbotX → Settings → Channels → connect your pages and IG account.

Telegram needs none of this: grab a bot token from @BotFather and paste it. Webchat is built in.

Supplemental platform note Meta access requirements can change by permission and use case. Before serving accounts outside app roles, check the current App Review and access-level requirements.
ChatbotX settingsInference

Step 7: AI replies on your own model (do this before Step 8)

Settings → AI → OpenAI Compatible provider → your endpoint URL, model name, key. Any OpenAI-compatible endpoint works: a local inference server, a gateway in front of your own hardware, whatever you run. This is the piece that separates self-hosted from the SaaS tools... the model reading your customers' messages is one you chose, on infrastructure you control.

Checkpoint Send a controlled test prompt from ChatbotX and confirm the selected model returns the expected reply before the keyword funnel can send real traffic into it.
ChatbotX + live testAutomation

Step 8: The keyword funnel (comment-to-DM automation)

The mechanic behind every "comment WORD and I'll DM you" post. This is a comment automation, not a flow. It is a settings form, not the visual builder. Direct routes (they're not top-level in the sidebar):

Direct routes
https://chatbotx.yourdomain.com/space/<workspaceId>/fb-comments
https://chatbotx.yourdomain.com/space/<workspaceId>/ig-comments

Build one per platform. The settings that matter:

  • Track comments on: "All posts" while testing, then re-target to the specific post once it's live. Otherwise every old post is a trigger surface.
  • Private reply → Text message: the deliverable link plus one line asking for a reply ("Reply DONE when you've got it"). Meta allows exactly one private reply per comment; their reply back is what opens the 24-hour window for anything further. That reply-back is how a keyword campaign becomes a warm list.
  • Public reply → Text message: short and visible ("check your DMs ✅"). The public replies are the social proof that makes later commenters pile in. Don't use Hide Comments for a keyword campaign. The comment wall is the engagement signal.
  • Keywords: exact match with case variants (build, BUILD, Build). Trailing punctuation matched fine in live testing (build! fired), but keep the CTA copy to one bare word regardless.
  • Toggles: Reply only once per user per post ON, Like the user comment ON.
  • Don't reply to replies: platform-dependent, and this one costs an evening if you miss it. Keep it ON for Instagram and turn it OFF for Facebook. On FB photo posts (any post with an image attached, such as your actual campaign post), Meta scopes the comment's parent_id with a different object prefix even for top-level comments, and ChatbotX's reply check compares the raw IDs. With the toggle on, every top-level comment on a photo post is falsely skipped as a "nested reply." The event arrives, nothing fires, no error anywhere.
  • DM link hygiene: point the private reply at a subdomain you own with a redirect (guide.yourdomain.com → wherever the deliverable lives), not the raw destination URL. The destination can move later without editing automations that already sent thousands of DMs.

Testing methodology (each of these bit during the real build):

  • Test from a separate account, not the page. Comments from the page itself are suppressed by design.
  • Post fresh comments for each test. Editing an old comment fires an edited event, which automations ignore.
  • A full pass is three things: the like on the comment, the public reply, and the DM.
  • Ground truth lives in the logs, not the UI:
Bash / host
docker logs $(docker ps -q --filter name=builder) 2>&1 | grep -iE 'comment|feed|webhook' | tail -20

Event present but no reply → ChatbotX config (keyword, targeting, the FB toggle above). No event → Meta side (wrong webhook object, feed not subscribed, page not reconnected).

Host + CloudflareLockdown

Step 9: Security checklist

  • Builder, realtime, RustFS, MailHog, and Adminer are bound to 127.0.0.1 (already done in the compose above). MailHog holds password-reset links for every account including yours, and ships with zero auth.
  • Public signup is open by design and cannot be disabled in config. MailHog prevents outsiders from receiving verification links, but public signup can still create abuse and resource-enumeration risk. Keep Cloudflare Access in front of the builder hostname unless public access is required. If Meta webhooks need a bypass, limit it to the exact integration paths and test that no account or admin route bypasses Access.
  • Webhooks are HMAC signature-verified against your Meta app secret. Platform credentials are encrypted at rest with ENCRYPTION_KEY.
  • No ports open on your router. All ingress via the tunnel.
Host exposure check The compose binds every browser-facing host port to 127.0.0.1. Verify that result with docker ps, keep router forwarding disabled, and confirm your provider firewall exposes none of those ports. Docker-published ports can bypass some host firewall rules, so the loopback binding is the primary control rather than a substitute for verification.

Prove the whole build works

Supplemental completion gate: one clean pass from container health through a real comment-to-DM reply.

Troubleshooting: every error from the real build

Search by symptom, error text, cause, or fix.

Data-loss warning Never delete a database or cache volume as a first troubleshooting step. Back it up, confirm the exact stack-prefixed target, and preserve it unless this is a disposable first boot.

15 issues shown

Symptoms, causes, and fixes from the working build
SymptomCauseFix
Crash loop: Invalid URL on DATABASE_URLbase64 password with / inside the connection stringgenerate a hex password, update POSTGRES_PASSWORD and DATABASE_URL together, then redeploy; preserve the existing volume
Invalid environment variables: ENCRYPTION_KEY / SMTP_FROM / REALTIME_BROADCAST_SECRETrequired vars missing from the docs composealready in the compose above
Postgres unhealthy: mkdir ... Permission deniedvolume created by a previous image run, wrong ownership; Docker only chowns empty volumesstop the stack, back up the volume, confirm the pinned image's expected UID/GID, repair ownership, and redeploy; recreate only a confirmed-disposable first-boot volume
502 through Cloudflarebuilder not up yet, or crash-loopingdocker logs <builder> | head -40. The validator names the missing var at the top
Coolify shows a service perpetually "Restarting"one-shot init container exits 0, Coolify relaunches itcosmetic; remove filesystem-init after first run
Compose editor rejects your editCoolify inlines YAML anchors and rewrites the file on save; your x- blocks are dead after first saveedit the expanded per-service environment: blocks directly
Signup/reset email "never arrives"it arrived in MailHogssh -L 8025:localhost:8025, open localhost:8025
"Invalid redirect_uri" connecting Instagramcallback not whitelisted, or whitelisted on the wrong Meta productStep 6.4: exact string, right product
Channel says "add settings to use this integration"platform credentials not entered, or /admin lockedPLATFORM_ADMIN_EMAIL env + Platform Credentials
Works for you, ignores real customersMeta app still in Development modeflip to Live
IG comments fire, FB comments never arrivefeed subscribed on the User object instead of PageMeta Activity log confirms; resubscribe feed under Page
FB comment event in logs, automation silent, no errorphoto-post parent_id prefix quirk + "Don't reply to replies" falsely skipping top-level commentsturn that toggle OFF on the FB automation (keep it on for IG)
Keyword test "stops working" on retryyou edited the old comment; edited events are ignoredpost a fresh comment each test
Automation fires for your tests, never for the page's own commentscomments from the page itself are suppressed by designtest from a separate account
Almost took a Live app downApp Mode header labels both toggle positions; filled toggle next to "Live" = already Liveif the dialog says "switch to development mode?", cancel