# Self-host open-source AI agents as one Docker Compose stack

> A self-hosted AI agent setup on Docker Compose is one Compose project and the order you start it in. Kortix is the open-source AI Operating System, and its stack moves from the three DNS records to five commands, then to the four failures that arrive once it is up.

URL: https://selfhostedaiagents.com/self-hosted-ai-agent-stack

## Five commands, in the order the stack expects

Preflight reads DNS, ports, disk and memory before anything is written; the rest assume it passed.

### Preflight

`preflight.sh`

```sh
$ ./bin/preflight.sh kortix.example.com
preflight passed
```

Source: https://raw.githubusercontent.com/ArtemShatokhin/selfhosted-ai-agents-stack/main/docs/install.md

### Install the CLI

`terminal`

```sh
$ curl -fsSL https://kortix.com/install | bash
```

Source: https://github.com/ArtemShatokhin/selfhosted-ai-agents-stack/blob/main/docs/install.md

### Initialize

`terminal`

```sh
$ kortix self-host init --domain kortix.example.com --admin-email you@example.com
```

Source: https://raw.githubusercontent.com/ArtemShatokhin/selfhosted-ai-agents-stack/main/docs/install.md

### Configure

`terminal`

```sh
$ kortix self-host configure
```

Source: https://raw.githubusercontent.com/ArtemShatokhin/selfhosted-ai-agents-stack/main/docs/install.md

### Start

`terminal`

```sh
$ kortix self-host start
```

Source: https://raw.githubusercontent.com/ArtemShatokhin/selfhosted-ai-agents-stack/main/docs/install.md

## What one Compose project holds

A self-hosted instance runs the whole platform on one server you control. `kortix self-host init` writes `docker-compose.yml`, `.env`, a `Caddyfile` and `updater.sh` into `~/.config/kortix/self-host/default/`, and one Compose project, `kortix-<instance>`, holds every service. The stack has no Redis and no separate worker.

Four services carry the request path. `frontend` is the web app. `kortix-api` is the REST API at `/v1`. `llm-gateway` is the standalone model gateway, and Caddy sends `api.<domain>/v1/llm*` to it. `kortix-migrate` is a one-shot job that migrates the database before `kortix-api` starts. Behind them sits `supabase-*`: Postgres as `supabase-db`, plus auth, REST, storage, realtime and the pooler, every image pinned by digest.

Two more services decide whether the stack is reachable at all. `caddy` terminates TLS on ports 80 and 443 with ACME HTTP-01 and routes traffic in domain mode. `kortix-updater` pulls new images once a day, migrates, and rolls the app services, and it mounts the Docker socket, which is why the update window gets a section of its own.

## Three DNS records, before the first start

All three point at the server: an `A` or `AAAA` for `<domain>`, the same for `api.<domain>`, and a wildcard `*.p.<domain>` for sandbox previews. Caddy asks for a certificate on the first request it receives, so the apex record has to resolve before `start`, or the first visitor meets a TLS error instead of a sign-in page.

The second record matters more than it looks. No session runs on your server. `kortix-api` creates each one on a Platinum sandbox with `PLATINUM_API_KEY`, and that sandbox calls back to the instance's public `KORTIX_URL` at `api.<domain>`. With no record there, the stack looks healthy and no session ever starts. The wildcard carries previews. Without it, previews fall back to a path proxy that breaks root-absolute links, and the doctor fails `preview-origins`.

`./bin/preflight.sh kortix.example.com` resolves all three, checks ports 80 and 443, disk, memory and tooling, and stops on a missing record. It is safe before the stack exists, and every step after it assumes that check passed. Run it again after any DNS or proxy change.

## The install writes its own compose file

```bash
./bin/preflight.sh kortix.example.com
curl -fsSL https://kortix.com/install | bash
kortix self-host init --domain kortix.example.com --admin-email you@example.com
kortix self-host configure
kortix self-host start
```

`init` asks the setup questions on a terminal and writes the files. `configure` asks them again and writes files only, so a run with no `start` after it changes nothing. Three answers carry the weight: the admin address, `PLATINUM_API_KEY`, and the preview domain `p.kortix.example.com`. Without a Platinum key the stack still starts and sessions do not, and `init` says so in a warning rather than stopping.

The admin address is a list, not a single value. Only addresses in `KORTIX_PLATFORM_ADMIN_EMAILS` reach `/admin`, and correcting a typo means repeating every address in the list.

## The memory floor is arithmetic

The published limits sum to `kortix-api` 2048m, `llm-gateway` 2048m, `frontend` 512m and Postgres fixed at 1280m. That is 5888MB before host memory, so `preflight.sh` fails under 6000MB, warns up to 7500 and passes at anything higher. Disk has a floor too: `kortix-updater` stops below `KORTIX_MIN_FREE_DISK_MB`, 2048 by default.

A 4GB server does not fail somewhere mysterious later. It fails in the preflight, before anything is written.

## What stays on your server

Self-hosted here means the control plane, the Postgres database and the stored files. Those live on the machine you rent, under your backups and your access rules. Sessions run on Platinum sandboxes that `kortix-api` creates with your key, and each sandbox calls back to the instance's public URL. Model calls go to the providers your projects connect, on keys you add in the web app after the first sign-in; each project keeps its own key.

So the split is worth stating plainly before anyone sizes a server around it. The box runs the platform, the database and the files. The sandboxes run the agents. The model providers see the prompts.

## The four failures that show up after the stack is up

### An open sign-up

A domain-mode instance is reachable by design, and a new one confirms sign-ups without sending mail. Until you set `EMAIL_URL` or `DISABLE_SIGNUP=true`, the auth service will confirm an account for any address that asks, including the address you made admin, and whoever claims an admin-listed address first becomes platform admin. `./bin/check-signup.sh https://kortix.example.com` reads the auth settings and exits 0 for closed or email-verified, 1 for open, 2 when it cannot tell. Run it after the first admin sign-in and again after every update, because an update is when a setting tends to return.

### A callback that does not resolve

This is the failure with the quietest symptom. Every service reports healthy, the web app loads, and each session a person starts sits at nothing. The cause is usually the `api.<domain>` record, or a proxy that answers on the apex and not the API subdomain. `preflight.sh` prints that failure by name, and the doctor catches the same missing record on its next run.

### A backup without its key

The instance directory is the unit to copy: `.env` holds the secrets and signing keys, `volumes/db/data` is Postgres, `volumes/storage` keeps files. A database dump captures one of those three and leaves the key behind. `API_KEY_SECRET` decrypts every stored secret, so an archive without it restores a stack that serves pages and cannot read its own credentials. `backup.sh` refuses to run without the key, stops the stack so the Postgres files are consistent, archives the directory, and writes a sha256 plus a 16-hex key fingerprint. `restore.sh` fingerprints the archived key and refuses a swap that would break every stored credential. Copy the `.tgz`, its `.sha256` and its `.manifest.json` off the server.

### A doctor nobody reads

`./bin/doctor.sh default` runs seven checks: docker, docker-compose, compose-config, instance-dir, email-url, preview-origins and open-sign-up. Six of seven is the usual first result, and the one that fails is the failure that would have surfaced after an update at the worst hour. The script prints a fix line per failure and keeps a timestamped report under `reports/`. Exit 0 means all seven passed.

## The update window, and the way back

`kortix-updater` runs at 02:00 in `America/New_York` unless `KORTIX_UPDATE_TIME` or `KORTIX_UPDATE_TZ` says otherwise. It pulls the channel, runs `kortix-migrate`, then rolls the API, the gateway and the front end. A failed migration swaps nothing, and the old version keeps answering. `kortix self-host status` shows the last outcome, including drift and the next scheduled run. When a release goes wrong anyway, `rollback --release <version>` returns to the version that worked.

None of this is the compose file, and the compose file is written for you. What separates a stack that runs in a demo from one a team depends on is whether those four readings get taken and whether a person reads the answer. The [operator kit built around the Kortix CLI](https://github.com/ArtemShatokhin/selfhosted-ai-agents-stack) exists for that, and `verify-all.sh` reruns sign-up, doctor, update status and backup age in one pass, exiting 1 when a person is needed. Every environment variable and the full command list are in [Read the docs](https://kortix.com/docs/host), and [what self-hosting costs](/self-hosted-ai-agents-cost) is worth reading before you pick a server size.

## Docker, self-hosting and local runs

### Can I use Docker to create AI agents?

Yes. Kortix installs as one Docker Compose stack, where Docker supplies the web app, the API, the model gateway and Postgres, and the agents live in the project’s git repo rather than in an image. `kortix self-host start` brings the stack up.

### Can you self host an AI agent?

Yes. Kortix is the open-source AI Operating System, and a self-hosted instance is the full platform on a server you control, with the control plane, database and files on your disk. Sessions run on Platinum sandboxes that call back to your instance, so plan for a public domain.

### Can I run an AI agent locally?

Yes, for evaluation. Kortix runs in local mode with every port on 127.0.0.1, and no session starts there because the sandbox cannot reach a private API. Use domain mode for anything a team depends on.

## Point DNS, run five commands, then read the four checks.

Self-host is free, and the preflight names what is missing before anything is written.

[Try Kortix](https://kortix.com) · [What self-hosting costs](https://selfhostedaiagents.com/self-hosted-ai-agents-cost)
