---
title: Deploy with Docker
description: "Run the full Feeblo stack with docker compose: what each service does, first boot, and the upgrade path."
---

The `docker-compose.yml` at the repo root defines a complete single-host deployment: API
server, dashboard, PostgreSQL, and Redis. Both app containers come from prebuilt GHCR
images, so nothing builds locally. The server container runs database migrations before
it starts listening, which turns upgrades into a pull-and-restart.

## Prerequisites

- Docker Engine and Docker Compose v2. Check yours with `docker compose version`.
- DNS records for three hosts: your dashboard URL, your API URL, and a wildcard record
  (`*.example.com`) covering board subdomains.
- SMTP credentials. New accounts must click a verification link by default, so mail has
  to flow before anyone can register.
- A way to generate random keys (`openssl` works).

## The services

| Service  | Image                                       | Host port (default) |
| -------- | ------------------------------------------- | ------------------- |
| `pg`     | `pgvector/pgvector:pg17`                    | 5432                |
| `redis`  | `redis:7-alpine`                            | 6379                |
| `server` | `ghcr.io/g3root/feeblo-server:$IMAGE_TAG`   | 8080                |
| `web`    | `ghcr.io/g3root/feeblo-web:$IMAGE_TAG`      | 4321                |

Details worth knowing before you boot:

- **Healthchecks gate startup.** Postgres reports ready via `pg_isready`, Redis via
  `redis-cli ping`. The server starts only after both pass; the web container waits for
  the server.
- **Migrations run on every server start.** The container's start command runs
  `node ./migrate/index.js` before launching the API, so schema changes apply
  themselves.
- **Data lives in named volumes** called `postgres` and `redis`. Deleting a volume
  deletes your data.
- **The server exposes `/health`**, returning `{"status":"ok","release":"..."}` once it
  listens.

:::warning[Set APP_URL, API_URL, and APP_ROOT_DOMAIN yourself]
The compose defaults point `APP_URL` at `http://localhost:3001` and `API_URL` at
`http://localhost:3000`, while the containers publish on ports 4321 and 8080.
Leave those three variables unset and sign-in fails in confusing ways: auth checks
browser origins against `AUTH_TRUSTED_ORIGINS`, which is derived from these same values.
Set them in `.env` to hosts that resolve from your browser and your server.
:::

## First boot

1. **Create your env file**

    Create `.env` next to `docker-compose.yml`:

    ```bash
    # Required. Compose refuses to start without it.
    # Generate with: openssl rand -hex 32
    AUTH_ENCRYPTION_KEY=

    # Public origins. These must match reality or sign-in breaks.
    APP_URL=https://app.example.com     # dashboard
    API_URL=https://api.example.com     # HTTP API
    APP_ROOT_DOMAIN=example.com         # boards live on <name>.example.com

    # Outgoing email. See the Email page for every option.
    SMTP_HOST=smtp.example.com
    SMTP_PORT=587
    SMTP_USERNAME=your-username
    SMTP_PASSWORD=your-password
    SMTP_FROM_ADDRESS=noreply@example.com
    ```

    You rarely need to set `AUTH_TRUSTED_ORIGINS`: compose derives a default of
    `$APP_URL,$API_URL,*.$APP_ROOT_DOMAIN`, and the wildcard covers every board subdomain.
    Add an override only when browsers call the API from extra origins; scheme-less patterns
    match both http and https hosts.

2. **Start the stack**

    ```bash
    docker compose up -d
    ```

    Postgres and Redis turn healthy first, the server runs migrations and comes up, then the
    dashboard follows. Watch progress with:

    ```bash
    docker compose ps
    ```

3. **Verify the API is alive**

    ```bash
    curl https://api.example.com/health
    ```

    A JSON body with `"status":"ok"` means migrations finished and the server listens.

4. **Create your account**

    Open `APP_URL` in a browser. Email and password sign-up is enabled by default
    (`AUTH_SIGN_UP_ENABLED`), so register, click the verification link that arrives by email,
    and sign in. GitHub and Google sign-in switch on as soon as you configure their client
    credentials (see [OAuth](/self-hosting/oauth)).

:::tip[Public host? Trim the port map]
Compose publishes Postgres (5432) and Redis (6379) to the host for local convenience. On
a machine with a public interface, delete those two `ports` entries from the compose file
so only `server` and `web` accept connections.
:::

## Upgrades

New app images arrive continuously; pin `IMAGE_TAG` to a specific GHCR tag instead of
`latest` so you decide when to move. To upgrade:

```bash
# Back up first
docker compose exec pg pg_dump -U feeblo feeblo > feeblo-backup.sql

# Pull new images and recreate containers; migrations run on server start
docker compose pull
docker compose up -d
```

To roll back, point `IMAGE_TAG` at the previous tag and run `docker compose up -d`
again. If the newer release migrated the schema past what the older image understands,
restore your backup into the `postgres` volume first.

### Changing embedding dimensions

Post embeddings default to OpenAI `text-embedding-3-small` at 1536 dimensions. When you
switch to a model with a different vector size, reconfigure the database column from the
published server image **before** restarting it:

```sh
docker compose run --rm server \
  node ./migrate/configure-embeddings.js \
  --dimensions 768 \
  --clear-existing
```

The command rebuilds the vector index for the new size. With `--clear-existing` it drops
vectors that no longer fit; without it, the command fails safely instead of losing data.

## Next steps

<CardGroup cols={2}>
  <Card title="Environment variables" href="/self-hosting/environment">
    Every variable Feeblo reads, grouped and marked required or optional.
  </Card>
  <Card title="Database" href="/self-hosting/database">
    Postgres specifics and the migration lifecycle.
  </Card>
  <Card title="Email" href="/self-hosting/email">
    SMTP transports, provider webhooks, outbox controls.
  </Card>
  <Card title="OAuth sign-in" href="/self-hosting/oauth">
    GitHub and Google login setup.
  </Card>
</CardGroup>
