> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sweetr.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Self-host

export const GitHubAppButton = () => <a href="https://github.com/settings/apps/new?name=sweetr&amp;url=https://YOUR_DOMAIN&amp;webhook_active=true&amp;webhook_url=https://YOUR_DOMAIN/api/github/webhook&amp;callback_urls[]=https://YOUR_DOMAIN/github/callback&amp;request_oauth_on_install=true&amp;metadata=read&amp;pull_requests=write&amp;statuses=write&amp;members=read&amp;emails=read&amp;events[]=installation_target&amp;events[]=organization&amp;events[]=public&amp;events[]=pull_request&amp;events[]=pull_request_review&amp;events[]=repository" target="_blank" rel="noopener noreferrer" className="not-prose" style={{
  display: "inline-flex",
  alignItems: "center",
  gap: 8,
  padding: "10px 20px",
  background: "#69db7c",
  color: "#000",
  borderRadius: 6,
  fontSize: 14,
  fontWeight: 600,
  textDecoration: "none",
  marginTop: 8,
  marginBottom: 8
}}>
    <Icon icon="brand-github" size={18} color="#000" />
    Create GitHub App
  </a>;

When you choose to self-host, the responsibility for updates, maintenance, and
merging future enhancements rests entirely with you.

<Tip>
  We highly recommend the <a href="/get-started/cloud">Cloud</a> version, unless
  you have hard requirements to manage your own infrastructure.
</Tip>

## Minimum System Requirements

| Resource | Minimum | Recommended |
| -------- | ------- | ----------- |
| CPU      | 2 vCPU  | 4 vCPU      |
| RAM      | 2 GB    | 4 GB        |
| Disk     | 20 GB   | 40 GB       |

**Software requirements:**

* Docker 24+ and Docker Compose v2+
* A domain with DNS A record pointing to your server
* Ports 80 and 443 open (for web traffic and TLS)

## Quick Start

The fastest way to get Sweetr running is with our deploy script. It will guide you through
the setup, including creating a GitHub App with the correct permissions.

```bash theme={null}
curl -fsSL https://raw.githubusercontent.com/sweetr-dev/sweetr.dev/main/bin/deploy | bash
```

The script will:

1. Check that Docker and Docker Compose are installed
2. Ask for your domain and GitHub organization handle
3. Generate a pre-filled URL to create a GitHub App with all required permissions
4. Collect your GitHub App credentials
5. Optionally configure Slack integration
6. Generate `docker-compose.yml` and `.env` files
7. Start the stack

To install a specific version, set the `SWEETR_TAG` environment variable:

```bash theme={null}
curl -fsSL https://raw.githubusercontent.com/sweetr-dev/sweetr.dev/1.2.0/bin/deploy | SWEETR_TAG=1.2.0 bash
```

## Manual Setup

If you prefer to set things up manually, follow the steps below.

### Prerequisites

<Steps>
  <Step title="Create a GitHub Application">
    You'll need to <a href="https://docs.github.com/en/apps/creating-github-apps/registering-a-github-app/registering-a-github-app">create a GitHub application</a>.

    Use our pre-filled link to speed this up — replace `YOUR_DOMAIN` with your actual domain after opening it.

    <GitHubAppButton />

    After opening the link, you must also:

    * Uncheck **"Expire user authorization tokens"**
    * Generate and set a **Webhook Secret**
    * Submit the form, then **generate a Client Secret**
    * **Generate a Private Key** (this downloads a `.pem` file)

    <Tip>
      Create the app under your **organization** account (not your personal account) so it can be installed on the organization directly. Use `https://github.com/organizations/YOUR_ORG/settings/apps/new` as the base URL.
    </Tip>

    <Accordion title="Manual setup (without pre-filled link)">
      <Tabs>
        <Tab title="General">
          * Generate a Client Secret
          * Callback URL: `https://your-domain.com/github/callback`
          * Uncheck "Expire user authorization tokens"
          * Check "Request user authorization (OAuth) during installation"
          * Webhook
            * Check "Active"
            * Webhook URL: `https://your-domain.com/api/github/webhook`
            * Generate and set a Webhook Secret
          * SSL verification
            * Check "Enable SSL verification"
          * Generate a Private Key
        </Tab>

        <Tab title="Permissions & events">
          #### Repository permissions

          * Metadata: Read-only
          * Pull requests: Read and Write
          * Commit Statuses: Read and Write

          #### Organization permissions

          * Members: Read-only

          #### Account permissions

          * Email addresses: Read-only

          #### Subscribe to events

          * Installation target
          * Organization
          * Public
          * Pull request
          * Pull request review
          * Repository
        </Tab>
      </Tabs>
    </Accordion>
  </Step>

  <Step title="Create a Slack App (Optional)">
    Create a Slack app at [https://api.slack.com/apps/new](https://api.slack.com/apps/new):

    <Tabs>
      <Tab title="OAuth & Permissions">
        * Create a Redirect URL: `https://your-domain.com/settings/integrations/slack`
        * Add the following Bot Token Scopes:
          * `app_mentions:read`
          * `channels:join`
          * `channels:read`
          * `chat:write`
          * `groups:read`
          * `im:read`
          * `im:write`
          * `mpim:read`
          * `reactions:read`
          * `users.profile:read`
          * `users:read`
          * `users:read.email`
      </Tab>

      <Tab title="Event Subscriptions">
        * Set "Enable Events" to ON
        * Change the "Request URL" to `https://your-domain.com/api/slack/webhook`
      </Tab>
    </Tabs>
  </Step>

  <Step title="Create a Caddyfile">
    Create a `Caddyfile` for the reverse proxy. Caddy automatically provisions and renews
    HTTPS certificates via Let's Encrypt.

    ```
    your-domain.com {
        @api path /api /api/*
        handle @api {
            uri strip_prefix /api
            reverse_proxy api:8000
        }
        handle {
            reverse_proxy web:80
        }
    }
    ```

    <Note>
      A public domain is required — GitHub cannot deliver webhooks to `localhost`. For local development, use a tunnel like [Cloudflare Tunnel](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/) or [ngrok](https://ngrok.com/) and use the tunnel URL as your domain.
    </Note>
  </Step>

  <Step title="Create docker-compose.yml">
    Create a `docker-compose.yml` file:

    ```yaml theme={null}
    services:
      caddy:
        image: caddy:2
        container_name: sweetr-caddy
        ports:
          - "80:80"
          - "443:443"
          - "443:443/udp"
        volumes:
          - ./Caddyfile:/etc/caddy/Caddyfile:ro
          - sweetr-caddy-data:/data
          - sweetr-caddy-config:/config
        restart: unless-stopped
        networks:
          - sweetr
        depends_on:
          - api
          - web

      api:
        image: sweetr/api:latest
        container_name: sweetr-api
        env_file: .env
        environment:
          - NODE_ENV=production
          - APP_ENV=production
          - APP_MODE=self-hosted
          - USE_SELF_SIGNED_SSL=false
        restart: unless-stopped
        networks:
          - sweetr
        depends_on:
          postgres:
            condition: service_healthy
          dragonfly:
            condition: service_healthy

      web:
        image: sweetr/web:latest
        container_name: sweetr-web
        environment:
          - API_ENDPOINT=${API_ENDPOINT}
          - AUTH_COOKIE_DOMAIN=${AUTH_COOKIE_DOMAIN}
          - GITHUB_APP=${GITHUB_APP}
          - SENTRY_DSN=${SENTRY_DSN:-}
          - APP_ENV=${APP_ENV:-production}
        restart: unless-stopped
        networks:
          - sweetr
        depends_on:
          - api

      postgres:
        image: postgres:16
        container_name: sweetr-postgres
        env_file: .env
        volumes:
          - sweetr-db:/var/lib/postgresql/data
        restart: unless-stopped
        networks:
          - sweetr
        healthcheck:
          test: ["CMD-SHELL", "pg_isready -U postgres"]
          interval: 10s
          timeout: 5s
          retries: 5

      dragonfly:
        image: docker.dragonflydb.io/dragonflydb/dragonfly
        container_name: sweetr-dragonfly
        command: ["--cluster_mode=emulated", "--lock_on_hashtags", "--proactor_threads=2"]
        ulimits:
          memlock: -1
        volumes:
          - sweetr-dragonfly:/data
        restart: unless-stopped
        networks:
          - sweetr
        healthcheck:
          test: ["CMD-SHELL", "redis-cli ping"]
          interval: 10s
          timeout: 5s
          retries: 5

    volumes:
      sweetr-db:
      sweetr-dragonfly:
      sweetr-caddy-data:
      sweetr-caddy-config:

    networks:
      sweetr:
        driver: bridge
    ```
  </Step>

  <Step title="Configure environment variables">
    Create a `.env` file. See the [Environment Variables Reference](#environment-variables-reference) below for all available options.

    ```bash theme={null}
    # Database
    POSTGRES_USER=postgres
    POSTGRES_PASSWORD=<generate-a-secure-password>
    POSTGRES_DB=sweetr
    DATABASE_URL=postgres://postgres:<POSTGRES_PASSWORD>@postgres:5432/sweetr

    # API
    PORT=8000
    FRONTEND_URL=https://your-domain.com
    JWT_SECRET=<generate-a-secure-random-string>
    REDIS_CONNECTION_STRING=redis://dragonfly:6379

    # GitHub App
    GITHUB_APP_ID=<your-app-id>
    GITHUB_CLIENT_ID=<your-client-id>
    GITHUB_CLIENT_SECRET=<your-client-secret>
    GITHUB_APP_HANDLE=<your-app-slug>
    # Private key: replace newlines with \n and wrap in double quotes
    GITHUB_APP_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\nMIIE...\n-----END RSA PRIVATE KEY-----\n"
    GITHUB_WEBHOOK_SECRET=<your-webhook-secret>

    # Web
    API_ENDPOINT=https://your-domain.com/api
    AUTH_COOKIE_DOMAIN=your-domain.com
    GITHUB_APP=<your-app-slug>

    # Slack (optional)
    SLACK_CLIENT_ID=
    SLACK_CLIENT_SECRET=
    SLACK_WEBHOOK_SECRET=
    ```
  </Step>

  <Step title="Start the stack">
    ```bash theme={null}
    docker compose up -d
    ```

    The API container will automatically run database migrations on startup.
  </Step>

  <Step title="Complete onboarding">
    After logging in, the initial data sync will begin. Follow the [Onboarding Guide](/get-started/onboarding) to review what was set up and configure your workspace.
  </Step>
</Steps>

## Environment Variables Reference

### API (`sweetr/api`)

| Variable                                          | Required | Default            | Description                                |
| ------------------------------------------------- | -------- | ------------------ | ------------------------------------------ |
| `DATABASE_URL`                                    | Yes      | —                  | PostgreSQL connection string               |
| `REDIS_CONNECTION_STRING`                         | Yes      | —                  | Redis/Dragonfly connection string          |
| `JWT_SECRET`                                      | Yes      | —                  | Secret for signing JWT tokens              |
| `FRONTEND_URL`                                    | Yes      | —                  | URL of the web application                 |
| `PORT`                                            | No       | `3000`             | API server port                            |
| `GITHUB_APP_ID`                                   | Yes      | —                  | GitHub App ID                              |
| `GITHUB_CLIENT_ID`                                | Yes      | —                  | GitHub OAuth Client ID                     |
| `GITHUB_CLIENT_SECRET`                            | Yes      | —                  | GitHub OAuth Client Secret                 |
| `GITHUB_APP_HANDLE`                               | Yes      | —                  | GitHub App slug (from the app URL)         |
| `GITHUB_APP_PRIVATE_KEY`                          | Yes      | —                  | GitHub App private key (PEM format)        |
| `GITHUB_WEBHOOK_SECRET`                           | Yes      | —                  | Secret for verifying GitHub webhooks       |
| `GITHUB_OAUTH_REDIRECT_PATH`                      | No       | `/github/callback` | OAuth redirect path                        |
| `APP_MODE`                                        | No       | `self-hosted`      | `self-hosted` or `saas`                    |
| `NODE_ENV`                                        | No       | `production`       | `development` or `production`              |
| `APP_ENV`                                         | No       | `production`       | `development`, `staging`, or `production`  |
| `LOG_LEVEL`                                       | No       | `info`             | `debug`, `info`, `warn`, or `error`        |
| `USE_SELF_SIGNED_SSL`                             | No       | `false`            | Use self-signed SSL certificates           |
| `BULLMQ_ENABLED`                                  | No       | `true`             | Enable background job processing           |
| `SLACK_CLIENT_ID`                                 | No       | `""`               | Slack Client ID                            |
| `SLACK_CLIENT_SECRET`                             | No       | `""`               | Slack Client Secret                        |
| `SLACK_WEBHOOK_SECRET`                            | No       | `""`               | Slack webhook verification secret          |
| `STRIPE_API_KEY`                                  | No       | `""`               | Stripe API key (cloud billing only)        |
| `STRIPE_WEBHOOK_SECRET`                           | No       | `""`               | Stripe webhook secret (cloud billing only) |
| `SENTRY_DSN`                                      | No       | `""`               | Sentry DSN for error tracking              |
| `LOG_DRAIN`                                       | No       | `console`          | `console` or `logtail`                     |
| `LOGTAIL_TOKEN`                                   | No       | `""`               | LogTail source token                       |
| `EMAIL_ENABLED`                                   | No       | `false`            | Enable transactional emails                |
| `RESEND_API_KEY`                                  | No       | `""`               | Resend API key for emails                  |
| `BULLBOARD_PATH`                                  | No       | `""`               | URL path to access BullBoard dashboard     |
| `BULLBOARD_USERNAME`                              | No       | `""`               | BullBoard login username                   |
| `BULLBOARD_PASSWORD`                              | No       | `""`               | BullBoard login password                   |
| `CRON_GITHUB_RETRY_FAILED_WEBHOOKS_EVERY_MINUTES` | No       | `30`               | Retry interval for failed webhooks         |

### Web (`sweetr/web`)

| Variable             | Required | Default      | Description                                                  |
| -------------------- | -------- | ------------ | ------------------------------------------------------------ |
| `API_ENDPOINT`       | Yes      | —            | Full URL to the API (e.g., `https://sweetr.example.com/api`) |
| `AUTH_COOKIE_DOMAIN` | Yes      | —            | Domain for auth cookies (e.g., `example.com`)                |
| `GITHUB_APP`         | Yes      | —            | GitHub App slug (same as `GITHUB_APP_HANDLE`)                |
| `SENTRY_DSN`         | No       | `""`         | Sentry DSN for frontend error tracking                       |
| `APP_ENV`            | No       | `production` | Environment name for Sentry                                  |

## Upgrading

To upgrade to a new version:

```bash theme={null}
cd sweetr
docker compose pull
docker compose up -d
```

Database migrations run automatically on API container startup.

To pin a specific version instead of `latest`, edit the image tags in `docker-compose.yml`:

```yaml theme={null}
services:
  api:
    image: sweetr/api:1.2.0
  web:
    image: sweetr/web:1.2.0
```

## Monitoring (Optional)

### Sentry

Add to your API `.env`:

```bash theme={null}
SENTRY_DSN=https://your-sentry-dsn
```

For frontend error tracking, also add to your `.env`:

```bash theme={null}
SENTRY_DSN=https://your-frontend-sentry-dsn
```

The web container reads this as the `SENTRY_DSN` environment variable.

### LogTail

```bash theme={null}
LOG_DRAIN=logtail
LOGTAIL_TOKEN=your-logtail-token
```

### BullBoard (Job Queue Dashboard)

To enable the BullBoard dashboard for monitoring background jobs:

```bash theme={null}
BULLBOARD_PATH=/bullboard
BULLBOARD_USERNAME=admin
BULLBOARD_PASSWORD=<secure-password>
```

Access it at `https://your-domain.com/api/bullboard`.

## Troubleshooting

### API fails to start

Check the container logs:

```bash theme={null}
docker compose logs api
```

Common causes:

* **Database connection failed**: Ensure Postgres is healthy with `docker compose ps`. The API waits for migrations to run on startup.
* **Missing environment variables**: The API will log which required variables are missing.
* **Redis connection failed**: Ensure Dragonfly is healthy. Check that `REDIS_CONNECTION_STRING` uses the Docker service name (e.g., `redis://dragonfly:6379`).

### Web app shows blank page

Check the browser console for errors. Common causes:

* **`API_ENDPOINT` is wrong**: Make sure it's the full URL reachable from the browser (not an internal Docker hostname).
* **CORS errors**: Ensure `FRONTEND_URL` on the API matches the URL you're accessing the web app from.

### Database migration errors

If migrations fail on startup, check the API logs. You may need to connect to Postgres directly:

```bash theme={null}
docker compose exec postgres psql -U postgres -d sweetr
```

### Port conflicts

If port 80 or 443 is already in use, either stop the conflicting service or change the Caddy port mappings in `docker-compose.yml`:

```yaml theme={null}
caddy:
  ports:
    - "8080:8080" # Change host port
```

Then update your `Caddyfile` to match (e.g., `:8080 {`) and adjust `FRONTEND_URL` and `API_ENDPOINT` in `.env` accordingly.
