---
title: Self-hosting
description: Run Dagr on your own machine with Docker Compose.
---

# Self-hosting

Use this when you want Dagr on hardware you control. For a first run, [Quick start](/quickstart) is enough. This page covers keeping that stack up, changing settings, and what to do before you expose it.

For CPU, memory, and disk sizes, see [Hosting](/hosting#recommended-resources).

To put the same services on Northflank instead, see [Northflank](/hosting/northflank).

## Start

From the repository root:

```bash
make compose-up
```

| Service | Port | Role |
| --- | --- | --- |
| Dagr API | `8080` | HTTP API (`/api/v1/*`) |
| Web UI | `4173` | Browser client (nginx) |
| Postgres | `5433` on the host, `5432` in Docker | Data |
| Redis | `6379` | Cache, pub/sub, background jobs |
| MinIO | `9000` (API), `9001` (console) | Object storage (wired later) |

Compose applies database migrations before the API starts.

Default database and MinIO passwords are for local use. Change them before anyone else can reach the server.

## Connect a client

Open `http://localhost:4173` in a browser, or start the Electron app with `make client-dev`. Choose **Self-hosted** and enter `http://localhost:8080` (or the host you published).

See [Web app](/hosting/web-app) and [Sign in](/desktop/sign-in).

## Settings

Copy `deploy/.env.example` to `deploy/.env`. `make compose-up` and local `make run` targets read that file.

You will usually want:

| Variable | What it does |
| --- | --- |
| `PUBLIC_BASE_URL` | Origin used in invite and email verification links. Local default is `http://localhost:4173`. |
| `SERVER_PUBLIC_URL` | Public URL of this API, if you expose it. |
| `SERVER_ID` | Stable name for this server. Set something of your own if you keep the process running. |
| `EMAIL_PROVIDER` | `log` (default, prints mail to the worker log), `smtp`, or `lettermint`. See [Email](/hosting/email). |
| `EMAIL_FROM` | Required when you use SMTP or Lettermint. |
| `LOG_LEVEL` | JSON log verbosity: `debug`, `info` (default), `warn`, or `error`. |

Password rules (`PASSWORD_MIN_LENGTH` and the `PASSWORD_REQUIRE_*` flags) are returned on `GET /api/v1/public/config` so the desktop app can show them.

Leave `DEPLOYMENT_MODE` unset. It defaults to `selfhosted`, which never talks to Mollie.

Full list: `deploy/.env.example`.

## Worker

Invites, verification email, scheduled messages, and link previews are handled by a worker, not by the API process. See [Worker](/hosting/worker).

Compose starts the API only. In another terminal:

```bash
make worker-run
```

That needs Redis (already running from Compose). With `EMAIL_PROVIDER=log`, nothing is sent; you read the message in the worker logs. To deliver real mail, see [Email](/hosting/email).

## Develop the API on the host

If you are changing Go code, keep dependencies in Docker and run the API locally:

```bash
make compose-infra   # Postgres, Redis, MinIO only
make migrate-up
make run-watch       # rebuilds on .go changes
```

Do not run `make compose-up` and `make run-watch` together. Both bind port `8080`.

## Check it

```bash
curl -s http://localhost:8080/api/v1/health
# {"status":"ok"}

curl -s http://localhost:8080/api/v1/public/config
```

## Stop

```bash
make compose-down
```

Volumes (`postgres_data`, `redis_data`, `minio_data`) stay until you remove them:

```bash
docker compose -f deploy/docker-compose.yml down -v
```

## Before you expose it

- Change default Postgres and MinIO passwords.
- Put TLS in front of port `8080`.
- Set `PUBLIC_BASE_URL` and `SERVER_PUBLIC_URL` to the URLs people will actually open.
- Set `SERVER_SIGNING_PRIVATE_KEY` so it does not change on every restart.
- Turn on real email if you want invites and verification to arrive in inboxes. See [Email](/hosting/email).
- Back up Postgres (and MinIO once file uploads are in use).

To run this stack behind a panel instead, see [Coolify](/hosting/coolify) or [Northflank](/hosting/northflank).
