Self-hosted & on-premise

The same binary, inside your own network.

attest_tag is open source under the MIT licence: one Go binary with the admin console embedded, one database, and signed HTTPS delivery from Slack and Microsoft Teams. Run it on a laptop with Docker in a few minutes, on Cloud Run, Fargate, Container Apps or Kubernetes with the script in the repository, or hand the install to a coding agent. Nothing about it is a lesser edition — it is the code the hosted service runs.

MIT licenceone binary, console embeddedyour model endpointyour keys

What stays inside

Where everything lives when you run it.

The on-premise question is six questions. Each answer is a mechanism the rest of this site explains; this is the checklist.

Threads stay in Slack, or in your database

A Slack thread is rebuilt from Slack on every turn rather than mirrored into a second copy of your workspace. Teams gives an app no way to read a thread back, so there the bot keeps its own record — in your database, on your infrastructure.

Documents are yours to point at

A folder, a bucket, or a shared Drive folder mirrored in. The chunks and the embeddings live in your database, and a document can be scoped to one channel.

Credentials are sealed with a key you hold

AES-256-GCM under a MASTER_KEY in your environment, injected by the proxy at the network edge, scrubbed back out of every response. A copy of the database is not a copy of your credentials.

The model call goes where you point it

OpenRouter, a vendor API, or a model you host, through the OpenAI-compatible API. Point it at your own host and nothing leaves the network at all.

The audit trail is a table in your database

Every turn, tool call, proxied request and completion — with tokens and cost — stored where you can query it, and exportable as CSV from the console.

Sign-in you control

Slack or Microsoft sign-in, or email and password with TOTP two-factor, admin, editor and viewer roles, invites that expire. ALLOWED_EMAIL_DOMAINS keeps guests and Slack Connect members out of the bot entirely.

Quick start

Four commands, one container, a public address.

That is a working deployment: one container, one volume, and a public HTTPS address with no account and no domain — the quick tunnel hands you one.

Then make your own Slack app. It belongs to your workspace and its token never leaves your deployment: paste the manifest from deploy/slack/manifest.json, point it at that address, and install it. Open the console and sign up; the first sign-up founds the deployment, and everybody after arrives by invitation.

On Microsoft Teams, instead or as well: register an Azure Bot — the free F0 tier — with its messaging endpoint at /msteams/messages on that address, and set the four MSTEAMS_ variables. guide/msteams.md in the repository walks through it.

The one thing that trips people up: Slack will not deliver events to localhost, and there is no Socket Mode fallback, so a public HTTPS address is required rather than convenient.

git clone https://github.com/Attest-Tag/attesttag && cd attesttag
./deploy/local/bootstrap.sh
docker compose --profile quicktunnel up -d
docker compose logs quicktunnel      # the https URL for your Slack app

bootstrap.sh writes .env and generates the MASTER_KEY, then tells you to back it up. Do that before anything else.

Platforms

One folder per place you can run it.

Each folder in deploy/ holds a README that covers that platform end to end and a script that is idempotent: rerunning it is how you ship a new version. Storage is local by default — a database and a folder of documents on a volume — and both can be something you bring: DATABASE_URL for any Postgres, DOCS_S3_URL for any S3-compatible bucket, which is what you move to when one instance is not enough.

deploy/local
Start here. Docker and docker compose on a laptop or one VM, with a quick tunnel for the HTTPS address. Also the macOS LaunchAgent.
deploy/gcp
Google Cloud Run with a bucket for documents and the database replica. This is what the hosted service runs.
deploy/aws
ECS Fargate behind an ALB, with documents and the SQLite replica in S3. Fargate rather than App Runner, because App Runner throttles idle CPU and the scheduler needs it.
deploy/azure
Azure Container Apps, with Postgres and Azure Files or a bucket elsewhere.
deploy/helm
Kubernetes, anywhere. A Helm chart with the workload, a Postgres and a MinIO for the cluster that has neither.

SQLite needs a local block device and exactly one writer — a Docker volume, an EBS disk, a Fargate task’s own storage or a Kubernetes RWO claim are fine; NFS, EFS and Azure Files are not. The fix-job worker is one WORKER_MODE and one script per platform.

With a coding agent

Or hand the install to an agent.

Clone the repository, open Claude Code, Codex, Cursor or any agent that runs shell commands in that directory, and say where you want it. The agent reads AGENTS.md, which sends it to the runbook — about ten minutes, most of it waiting for a container to build.

Three things need a human, and an agent that claims otherwise is wrong: creating the Slack app in your workspace (or the Azure Bot, for Teams), signing in to your cloud, and backing up the master key. The runbook stops for each.

git clone https://github.com/Attest-Tag/attesttag && cd attesttag
# then, to the agent, in that directory:
#   Read AGENTS.md, then install this on Google Cloud.

Rules the runbook gives it

  • Never print a secret, paste one into chat, or commit one. .env is gitignored and values are written with a shell redirect.
  • Stop before creating anything billable and show the list first.
  • Never deploy to a project or account you did not name — not the CLI's currently selected one, unless you said so.
  • Never run the GCP scripts against the maintainer's hosted service; a self-host passes its own env file.
  • Stop and report rather than improvise when a script fails.

Agents are not bound by a document. Run it where you can see what it does, and read the confirmation prompts rather than clicking through them.

What it needs

Three things decide whether a deployment works.

They are the same three on every platform, and every folder's README says so before anything else.

A public HTTPS origin

Slack will not deliver events to localhost and will not accept an http redirect URL, and there is no Socket Mode fallback; Teams delivers to an HTTPS messaging endpoint the same way. The quick tunnel needs no account and takes two minutes; a domain or a tailnet funnel are the other two ways.

Always-on CPU

The Slack inbox dispatcher, the routine scheduler, the ingest loop and Drive sync run between requests. A platform that freezes an idle container acks Slack's event and then never does the work — which looks like the bot ignoring people. Pin a warm instance.

MASTER_KEY, backed up somewhere else

It seals every stored credential. Lose it and every workspace and every connection has to be reconnected by hand — by design, there is no recovery. bootstrap.sh generates one and then tells you to back it up; that instruction is the important half.

And from you

A Slack workspace or a Teams organisation
And the ability to install an app into it — for Slack an owner, admin or approver; for Teams an Azure subscription to register the bot in, and a Teams admin to upload the app.
An LLM key
An OpenRouter key, or any OpenAI-compatible endpoint through LLM_BASE_URL — including one you host.
For a cloud install
An authenticated CLI and a project or account that can be billed. AWS also wants a domain you can add a CNAME to; Azure and GCP hand out an https hostname.
Storage
Nothing, to start: a local volume holds the database and the documents. Bring a Postgres and a bucket when you want more than one replica or your platform gives a container no block device.

Hosted vs self-hosted

Same code. The difference is whose bill it lands on.

The hosted service runs the repository as it is. Nothing you learn on one applies any less to the other.

Where it runs
Hosted: our Cloud Run service. Self-hosted: your infrastructure — a laptop, a VM, a cloud, a cluster.
Who runs it
Hosted: we do. Self-hosted: you do, as one container; rerunning the platform script is how you ship a new version.
The model
Hosted: the default open-weight model is included and metered against your plan's budget. Self-hosted: your endpoint and your key — OpenRouter, a vendor, or your own host.
The Slack app
Hosted: ours, installed from the console with a consent screen. Self-hosted: your own app in your workspace, from the manifest in the repository; its token never leaves your deployment.
The Teams app
Hosted: ours — a Teams admin uploads the package the console builds. Self-hosted: your own Azure Bot registration, on the free F0 tier, and four MSTEAMS_ settings; the package your console builds names your bot and your host.
Credentials
Both: sealed at rest, injected at the edge, never in a prompt. Self-hosted, the key that seals them is yours.
Price
Hosted: a free plan, then a flat fee by plan size plus prepaid AI credit, bought in the console. Self-hosted: MIT licence, no fee, no limits we set — or quoted, if you would rather we did the installing on your premises.
Fix-job worker
Both. Self-hosted, one WORKER_MODE and one script per platform turns it on.
The console
The same screens, at /admin, either way.

Start hosted and move on-premise later, or the other way round: the Slack app, the connections and the documents are each a few minutes to set up again, and nothing else has to move. Would rather we did the installing? We deploy it on your premises and hand you the keys — talk to us, or see pricing.

Clone it, or sign up. Same binary either way.

The repository is the product. Issues and pull requests are welcome, and the hosted service is there for the day you would rather not run it.

MIT-licensed. What setup involves