A service README now describes only its own service: no links to other services or to the root, and no restating of the shared conventions that the root README and CLAUDE.md already carry. Each service is a separate Coolify app on a <service>/** watch path, so a cross-link made editing one service redeploy another. The rule is recorded at the root; the alloy compose comment now points at a heading that exists.
4.3 KiB
goclaw
GoClaw is a multi-tenant AI agent gateway in Go: LLM providers, messaging channels (Telegram, Discord, Slack, Zalo, Feishu, WhatsApp), agent teams, and a knowledge vault, behind one binary with a built-in web dashboard.
Two containers: goclaw, and postgres — pgvector, which holds tenants,
sessions, encrypted provider keys and the semantic memory.
Setup
-
Generate the two secrets:
openssl rand -hex 16 # GOCLAW_GATEWAY_TOKEN openssl rand -hex 32 # GOCLAW_ENCRYPTION_KEY -
Map the domain to port
18790and deploy. Migrations run on start. -
Open the domain and use the setup wizard to add an LLM provider key.
Health check: GET /health.
Authentication
GOCLAW_GATEWAY_TOKEN is the bearer token for the API and the dashboard, and
the only thing between the domain and a gateway whose agents run tools. It
fails fast if unset rather than defaulting to blank: upstream's own compose
allows an empty token for loopback-only development, which behind a public
domain would publish the gateway.
GOCLAW_ENCRYPTION_KEY encrypts the provider API keys at rest in PostgreSQL
(AES-256-GCM). It is required for the same reason, and it must not change once
keys are stored — every one of them becomes unreadable.
Both are the two values upstream's prepare-env.sh generates; the script
itself is not used here.
Environment
| Variable | Default | Purpose |
|---|---|---|
GOCLAW_GATEWAY_TOKEN |
— | Bearer token for the API and dashboard |
GOCLAW_ENCRYPTION_KEY |
— | AES-256-GCM key for stored provider keys |
POSTGRES_USER / POSTGRES_PASSWORD / POSTGRES_DB |
goclaw |
Database credentials, read by both containers |
GOCLAW_TRACE_VERBOSE |
0 |
Log full LLM request and response bodies |
GOCLAW_HOST, GOCLAW_PORT, GOCLAW_CONFIG and GOCLAW_SKILLS_DIR are set
in compose.yml rather than here — they are properties of this layout, not of
whoever deploys it. The two paths override image defaults that point outside
any volume (/app/config.json, /app/skills), which would drop the
configuration and every installed skill on redeploy.
Provider API keys are not variables: they are entered in the dashboard and stored encrypted in PostgreSQL.
Storage
| Volume | Mount | Holds |
|---|---|---|
goclaw-data |
/app/data |
config.json, skills, Claude CLI credentials, runtime packages |
goclaw-workspace |
/app/workspace |
Files the agents work on |
postgres-data |
/var/lib/postgresql |
The database |
Upstream's PostgreSQL overlay adds a third goclaw-skills volume at
/app/skills; it is not here, because GOCLAW_SKILLS_DIR points at
/app/data/skills and the volume would mount empty over a path nothing reads.
Skills persist in goclaw-data instead.
postgres-data mounts /var/lib/postgresql, not /var/lib/postgresql/data.
PostgreSQL 18 images moved PGDATA down a level into a version directory, so
the parent is now the mount point.
The image's own user owns /app — goclaw, uid 1000, created with /app as
its home. /app/.claude is a symlink into /app/data, which is how CLI
credentials survive a recreate.
Networking
Listens on 18790.
extra_hosts maps host.docker.internal to the host gateway, so an agent can
reach a service running on the host itself.
Image
ghcr.io/nextlevelbuilder/goclaw:full, the variant with every runtime and
skill dependency pre-installed — Python, Node, pandoc, poppler, the GitHub CLI.
The plain tag ships Python only and installs the rest on demand into
/app/data/.runtime, which means the first use of a skill waits on a package
install and a wiped data volume repeats it. Paying for the size once is the
better trade here.
The tag is moving, tracking the newest full build. The variant tags are only
published as full and <major>.<minor>-full — there is no <major>-full —
so full is the moving tag to follow.
Hardening
cap_drop: ALL with SETUID/SETGID/CHOWN added back, no-new-privileges,
a noexec tmpfs on /tmp, and memory, CPU and pid limits — all as upstream
ships them. The three capabilities are what the entrypoint needs to install
persisted packages as root and then drop to the goclaw user via su-exec.
This is a service whose whole purpose is running model-chosen tools, so the limits are worth keeping.