Outbound OS
Browse documentation

Install Outbound OS with Docker

Updated 24 October 2026 · For version 0.3.0 · Edit on GitHub

Outbound OS runs as a Docker Compose app: one container for the API and dashboard, and one named volume for the database. This guide takes a fresh machine to a signed-in admin in about five minutes, then puts it behind HTTPS so WhatsApp can deliver replies.

What you need

  • Docker Engine with Compose v2 — on a Linux server, a Mac or Windows. docker compose version should print a version.
  • 1–2 GB of memory and a few gigabytes of disk. A small cloud server is plenty for a team.
  • A domain name, when you are ready to connect WhatsApp. Meta only delivers webhooks to a public HTTPS address. Trying it locally first needs neither.

1. Download and start

Terminal
git clone https://github.com/thatsarpit/outbound-os.git
cd outbound-os
cp .env.example .env
docker compose up -d

Before the last command, open .env and set ADMIN_EMAIL to the address you will sign in with. Every other setting can wait — the configuration reference lists them.

The first start downloads the ready-made image (for Intel and ARM servers), creates the database, runs the migrations and generates the instance’s secret keys. It usually takes under a minute.

2. Sign in

Open http://localhost:3001 (or your server’s address on port 3001) and sign in with ADMIN_EMAIL. If you did not set ADMIN_PASSWORD, a generated password was printed once in the log:

Terminal
docker compose logs app | grep "First admin"

Change it from your account page after signing in.

Hosted alternative: Render Blueprint

Deploy to Render

  1. Sign in to Render and open the deploy button. Review the service and disk charges before creating the Blueprint.
  2. Enter ADMIN_EMAIL when prompted. The Blueprint uses the published image, built-in sign-in and a persistent disk mounted at /app/data.
  3. Wait for the /healthz check to pass, then open the service’s HTTPS URL.
  4. Open the service’s Logs and search for First admin created. Sign in with the generated password and change it from your account page.
  5. Run the setup wizard. Configure channel credentials and webhook secrets only when you are ready to connect those providers.

A paid service and disk are required. Keep DATABASE_URL=file:/app/data/outboundos.dband the disk mount unchanged: the database and generated encryption keys must survive together. Use one service instance for this SQLite deployment. Fork the repository and pin a released image tag in render.yaml for controlled upgrades. Render supplies HTTPS automatically. See backups and upgrades before redeploying.

3. Run the setup wizard

The first sign-in opens the setup wizard. It asks for your business name, time zone, currency and default country code, which lead sources you use, and walks you through your first channel. Nothing sends until you connect a channel.

4. Put it behind HTTPS

For WhatsApp replies, Brevo events and website forms on other domains, the app needs a public HTTPS address. Any reverse proxy works. With Caddy, which fetches the certificate for you, the whole configuration is:

Caddyfile
crm.example.com {
  reverse_proxy localhost:3001
}

Nginx

On an Ubuntu or Debian server, install Nginx and Certbot, replace crm.example.com with your domain, and save this as /etc/nginx/sites-available/outbound-os. Point the domain’s DNS at the server and allow ports 80 and 443. In docker-compose.yml, replace the app’s port entry with "127.0.0.1:3001:3001", then run docker compose up -d, so visitors reach it through Nginx.

/etc/nginx/sites-available/outbound-os
server {
    listen 80;
    server_name crm.example.com;

    proxy_http_version 1.1;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header Connection "";

    location = /api/events {
        proxy_pass http://127.0.0.1:3001;
        proxy_buffering off;
        proxy_cache off;
        proxy_read_timeout 3600s;
    }

    location / {
        proxy_pass http://127.0.0.1:3001;
    }
}
Terminal
sudo apt-get update
sudo apt-get install nginx certbot python3-certbot-nginx
sudo ln -s /etc/nginx/sites-available/outbound-os /etc/nginx/sites-enabled/outbound-os
sudo nginx -t && sudo systemctl reload nginx
sudo certbot --nginx -d crm.example.com
sudo certbot renew --dry-run

Certbot adds the TLS certificate and HTTPS redirect. The separate /api/eventslocation disables buffering and keeps the live event stream open. The forwarded headers preserve the public scheme and client address. If Node runs directly on the host, set TRUST_PROXY=loopback. With this Docker setup, host Nginx normally reaches the app through Docker’s bridge gateway. Find that address with the command below, set TRUST_PROXY in .env to it, then recreate the app with docker compose up -d. For other networks or a CDN, configure only the actual trusted proxy addresses. Do not trust arbitrary client-supplied headers.

Find the Docker bridge gateway
docker inspect "$(docker compose ps -q app)" --format '{{range .NetworkSettings.Networks}}{{.Gateway}}{{end}}'

This configuration was exercised on Debian 12 with Nginx 1.28.0 in isolated Docker containers on October 12, 2026. CI checks forwarded headers, authentication and unbuffered event delivery using npm run test:proxy. Certificate issuance and renewal still require your public domain and DNS. The sanitized results are in docs/validation/nginx-proxy.json.

No open ports? A Cloudflare Tunnel pointed at http://localhost:3001 gives you an HTTPS address without exposing the machine. Behind a CDN, set TRUST_PROXY to the number of proxy hops so rate limits see real client addresses.

5. Connect your channels

  • Connect the WhatsApp Cloud API — about fifteen minutes in Meta’s developer console.
  • Email — Settings → Email → Add account, with SMTP and IMAP details or a Brevo key.
  • Send leads in from your website forms, ads and marketplaces.

Troubleshooting

The page does not load

Check the container is running and healthy with docker compose ps, and read the last lines of docker compose logs app. Port 3001 already in use? Set OUTBOUNDOS_PORT in .env to another port and start again.

I lost the admin password

Set a new one from the server. The script asks for the password instead of taking it on the command line, so it stays out of your shell history:

Terminal
docker compose exec -it app node scripts/reset-password.mjs you@example.com

Saved credentials stopped working after a move

The database was restored without its instance-secrets.json. See backups and upgrades.