Self-host and operate

Single container

The all-in-one image runs the whole stack in one container: Postgres, Redis, Evolution API, the web app, and the Telegram bridge share one image and one volume, and every secret is generated on first boot.

Run it

docker run -d --name automata --restart unless-stopped --stop-timeout 30 \
  -p 3000:3000 -v automata_data:/data \
  -e NUXT_PUBLIC_APP_URL=https://automata.example.com \
  ghcr.io/ju-li/automata-mcp-aio:1

Open the app on port 3000 and sign up. The first account creates an organization and becomes its administrator. First boot creates the database and runs Evolution's and the app's migrations; docker logs -f automata shows each service starting.

NUXT_PUBLIC_APP_URL is the address people will open, and every connector URL is built from it. Left out, it defaults to http://localhost:3000, which is fine for trying it and wrong for anything Claude has to reach.

--stop-timeout 30 gives the services time to stop in order: the web app, Evolution, and the bridge first, Postgres last. Docker's default of 10 seconds can cut that short.

What is inside

ProcessVersionListens on (inside the container)
Web appThis release3000, or $PORT. The only port to publish
Postgres16127.0.0.1:5432
Redis8.2.9127.0.0.1:6379
Evolution APIv2.3.718080
Telegram bridgeThis release18095

The image is built on Evolution's own image, so Evolution runs exactly as it ships. s6-overlay starts the rest in order, restarts what crashes, and stops the container if a start-up step fails. The image is large, about 2 GB, most of it Evolution's.

PORT may not be one of the internal ports above; the container refuses to start and says so.

Everything lives in /data

PathHolds
/data/postgresThe database: every user, connection, stored credential, and synced message
/data/redisEvolution's cache
/data/evolutionEvolution's session files
/data/secretsThe generated secrets, one file each

On first boot the container generates POSTGRES_PASSWORD, WEBHOOK_SECRET, EVOLUTION_API_KEY, TELEGRAM_BRIDGE_ADMIN_KEY, and TELEGRAM_SESSION_ENCRYPTION_KEY into /data/secrets. An environment variable with one of those names overrides the stored file, which is left as it was. Set them only if you manage secrets elsewhere, keep them URL-safe (openssl rand -hex 32), and never change TELEGRAM_SESSION_ENCRYPTION_KEY after the first boot.

Options

Everything is optional. The names are the ones .env.selfhost.example uses, so a filled-in copy works as docker run --env-file .env …; blank values are ignored.

VariableFor
NUXT_PUBLIC_APP_URLThe public address, as above
TELEGRAM_API_ID, TELEGRAM_API_HASHTelegram, from my.telegram.org. Both or neither. Without them the app tells an administrator what to set. See Telegram
SMTP_HOST, SMTP_PORT, SMTP_USERNAME, SMTP_PASSWORD, SMTP_TLS, MAIL_FROM, MAIL_FROM_NAMEConnection alerts, invitation emails, and email verification
NUXT_ALLOW_PRIVATE_TARGETStrue lets connections reach private and loopback addresses. See below
SIGNUP, SIGNUP_CODEWho can create an account. See Who can sign up
EMAIL_VERIFICATIONauto, required, or off. See Email verification
PORTThe web app's port inside the container. Defaults to 3000

Updating

docker pull ghcr.io/ju-li/automata-mcp-aio:1
docker rm -f automata
docker run -d --name automata …   # the same command as before

Removing the container and running it again with the same volume is safe. Migrations run at boot and only go forward, so moving to a new major is a deliberate edit of the tag. See Releases.

When to use Compose instead

Choose Docker Compose when you want to restart, upgrade, or back up one service without the others, put Postgres on its own disk, or leave out WhatsApp or Telegram entirely. The single container always runs all of them.

View source