---
title: "Self-host with Docker"
description: "Run the Fabro server as a Docker container with docker compose, ECS, or any cloud container service"
---

<Warning>
  The server interface is in private early access. Contact [bryan@qlty.sh](mailto:bryan@qlty.sh) if you're interested in trying it.
</Warning>

The supported deployment artifact is the official Fabro image at `ghcr.io/fabro-sh/fabro`. Everything else — `docker compose`, ECS, Cloud Run, Kubernetes, Railway — is just running this image somewhere with the right requirements.

## Requirements

| Requirement | Value |
|---|---|
| **Image** | `ghcr.io/fabro-sh/fabro:nightly` (multi-arch; pin a version for production) |
| **Persistent volume** | Mount at `/storage`. Stores run history, checkpoints, sessions, the dev token, and JWT keys. |
| **Port** | The container binds to `$PORT` (default `32276`). Expose it. |
| **LLM provider key** | Add at least one provider key during the install wizard or later with `fabro secret set` / `fabro provider login`. |
| **Replicas** | One. The server expects exclusive ownership of `/storage`. |

## Quickstart with docker compose

The repo ships a `docker-compose.yaml` at the root. Clone the repo (or copy the file), create a `.env` for bootstrap settings if needed, and start it:

```bash
git clone https://github.com/fabro-sh/fabro.git
cd fabro
cp .env.example .env
# edit .env for bootstrap values such as SESSION_SECRET or FABRO_DEV_TOKEN if needed
docker compose up -d
```

The compose file:

- Pulls `ghcr.io/fabro-sh/fabro:nightly`
- Creates a named volume `fabro-storage` mounted at `/storage`
- Mounts `/var/run/docker.sock` so Fabro can spawn sandbox containers on the host daemon
- Exposes port `32276`
- Loads environment from `.env` if present

<Warning>
  Mounting `/var/run/docker.sock` gives the container host-root-equivalent access. Only use the bundled compose service in trusted, single-tenant deployments. See [Sandboxing](/administration/sandboxing) for the threat model.
</Warning>

After the container is healthy, finish setup in your browser following the [install wizard](/reference/server-operations#first-run-web-install-wizard).

### Adding a reverse proxy with TLS

For a production deployment exposed to the internet, layer the `docker-compose.prod.yaml` overlay on top. It adds a [Caddy](https://caddyserver.com) reverse proxy that terminates TLS (auto-provisioning a Let's Encrypt certificate) and forwards to Fabro:

```bash
# In .env
FABRO_DOMAIN=fabro.example.com

# Then bring the stack up
docker compose -f docker-compose.yaml -f docker-compose.prod.yaml up -d
```

Leave `FABRO_DOMAIN` unset to serve plain HTTP on `localhost`.

### Private access with Tailscale Services

For a private tailnet deployment, use [Tailscale Services](https://tailscale.com/docs/features/tailscale-services) instead of Caddy. Tailscale terminates HTTPS on the tailnet DNS name and forwards to Fabro over host loopback. In this mode there is no Caddy container, no Let's Encrypt certificate, and no public DNS record for the Fabro app.

Configure the canonical external origin in `.env`:

```bash
FABRO_WEB_URL=https://fabro-testing.example.ts.net
```

`FABRO_WEB_URL` must be the Tailscale Service HTTPS origin with no trailing slash. Fabro uses it for install-mode links, browser auth, API links, and run URLs.

Start the loopback-only compose stack:

```bash
docker compose -f docker-compose.tailscale.yaml up -d
```

Then publish the service from the host:

```bash
tailscale serve --service=svc:fabro-testing --https=443 http://127.0.0.1:${FABRO_PORT:-32276}
tailscale serve status --json
```

The host must be joined to the tailnet with a device identity allowed to advertise the selected service. Depending on the tailnet policy, a tailnet admin may need to approve the service before the HTTPS name is reachable.

<Warning>
  Tailscale Services is private tailnet ingress. GitHub webhook deliveries from github.com cannot reach it. If your Fabro deployment needs GitHub webhooks, use `server_url` with a public webhook endpoint, `tailscale_funnel`, or another public relay for the webhook path.
</Warning>

## Bootstrap environment variables

For the web UI you need a session secret unless install mode is generating the initial local configuration:

```bash
SESSION_SECRET=<64-character hex string>
```

Generate one with `openssl rand -hex 32`.

`server.env` and container process env are for bootstrap values only:

| Variable | Purpose |
|---|---|
| `SESSION_SECRET` | Session encryption secret |
| `FABRO_DEV_TOKEN` | Optional — pre-set the dev token instead of reading the one written to `/storage` on first boot |
| `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_SESSION_TOKEN` | Optional static S3 object-store credentials |

Do not put optional integration secrets in `.env` for server runtime. Configure LLM provider keys, Slack, Daytona, Brave Search, Venice Search, `GITHUB_TOKEN`, and GitHub App secrets in the vault with `fabro secret set`, `fabro provider login`, or `fabro install`.

Optional:

| Variable | Purpose |
|---|---|
| `FABRO_DOMAIN` | Public hostname when using the Caddy reverse-proxy overlay |
| `FABRO_WEB_URL` | Canonical external web origin when TLS is terminated by a platform layer such as Tailscale Services |

See [Server Configuration](/administration/server-configuration) for the full settings reference, and [`.env.example`](https://github.com/fabro-sh/fabro/blob/main/.env.example) for the complete list.

## Cloud container services

The same image works on any container orchestrator that supports the requirements above. Common patterns:

- **AWS ECS / Fargate** — Task definition referencing `ghcr.io/fabro-sh/fabro:nightly`, EFS volume mounted at `/storage`, port `32276` published, environment variables for bootstrap values, and vault-backed optional integration secrets.
- **Google Cloud Run** — Cloud Run with a backed volume mount at `/storage`. Pin minimum instances to 1; scale-to-zero interrupts running workflows.
- **Kubernetes** — One-replica `StatefulSet` (not Deployment) with a `PersistentVolumeClaim` mounted at `/storage`. Expose via Service + Ingress.

In all cases: single replica, persistent `/storage`, expose `$PORT`, and configure optional integration secrets in the vault.

## Pinning a version

`docker-compose.yaml` uses `:nightly` by default, so `docker compose pull && docker compose up -d` picks up the latest nightly. To pin a specific version, change the `image:` line to `ghcr.io/fabro-sh/fabro:<version>`.

Release artifacts ship with [SLSA Build Provenance](/reference/verifying-releases) attestations you can verify with `gh attestation verify`.

## Pointing the CLI at your server

Once the container is running, install the CLI on your local machine and point it at the server:

```toml title="~/.fabro/settings.toml"
[cli.target]
type = "http"
url = "https://fabro.example.com/api/v1"
```

For dev-token auth, save the token in the CLI auth store:

```bash
fabro auth login --server https://fabro.example.com/api/v1 --dev-token fabro_dev_...
```

See [Server Operations](/reference/server-operations#pointing-the-cli-at-a-server) for the full CLI-target options.

## Caveats

- **Volume is load-bearing.** Without a persistent volume at `/storage`, redeploys silently wipe all state — including the dev token and JWT signing keys.
- **Single replica.** The server expects exclusive ownership of `/storage`. Don't scale to multiple replicas.
- **Architecture.** The `:nightly` tag is multi-arch. The amd64 variant is the most heavily tested.

## Next steps

<Columns cols={2}>
  <Card title="Server Operations" icon="server" href="/reference/server-operations">
    Install wizard, web UI, authentication, demo mode, and pointing the CLI at the server.
  </Card>
  <Card title="Server Configuration" icon="gear" href="/administration/server-configuration">
    Full settings.toml reference — auth, reverse-proxy TLS, run defaults, and more.
  </Card>
  <Card title="Deploy to Railway" icon="train" href="/administration/deploy-railway">
    One-click managed shortcut for the same Docker image.
  </Card>
  <Card title="Sandboxing" icon="shield" href="/administration/sandboxing">
    The Docker sandbox provider's security model and trust assumptions.
  </Card>
</Columns>
