# AgentRQ Self-Hosting Setup Guide

> Agent-friendly guide for setting up AgentRQ locally using Docker.
> Source: [Self-Hosting Docs](https://agentrq.com/docs/howto-self-hosting)

---

## Prerequisites

| Requirement | Details |
|---|---|
| **Docker** | Only runtime dependency. Install from [docs.docker.com/get-docker](https://docs.docker.com/get-docker/) |
| **Google OAuth2 credentials** | Client ID + Client Secret from [Google Cloud Console](https://console.cloud.google.com/apis/credentials) |
| **Domain or localhost** | `http://localhost` for local dev; a public domain for production |

---

## Quick Start (Development / Local)

### 1. Pull the image

```bash
docker pull agentrq/agentrq:latest
```

### 2. Create the `.env` file

Create a `.env` file in your working directory:

```env
# ── App ──────────────────────────────────────────────────
ENV=production
PORT=2026
AGENTRQ_BASE_URL=http://localhost:2026
AGENTRQ_DOMAIN=localhost

# ── Reverse proxy (optional) ─────────────────────────────
# Sub-path the proxy serves AgentRQ under (e.g. /agentrq); the proxy strips it
AGENTRQ_BASE_PATH=
# Public domain of the proxy; requests with this Host skip subdomain checks
AGENTRQ_PROXY_DOMAIN=
# Force the cookie Secure flag when the proxy terminates TLS (default: follows SSL_ENABLED)
AGENTRQ_COOKIE_SECURE=

# ── TLS ──────────────────────────────────────────────────
# Disabled for local dev
AGENTRQ_SSL_ENABLED=false
AGENTRQ_SSL_LETSENCRYPT_EMAIL=
AGENTRQ_SSL_CACHE_DIR=/_certs
AGENTRQ_SSL_CLOUDFLARE_API_TOKEN=

# ── Database: SQLite (default, zero setup) ───────────────
AGENTRQ_SQLITE_ENABLED=true
AGENTRQ_SQLITE_DSN=./_storage/agentrq.db

# ── Database: PostgreSQL (disabled for local dev) ────────
AGENTRQ_POSTGRES_ENABLED=false
AGENTRQ_POSTGRES_HOST=localhost
AGENTRQ_POSTGRES_PORT=5432
AGENTRQ_POSTGRES_USER=postgres
AGENTRQ_POSTGRES_PASSWORD=yourpassword
AGENTRQ_POSTGRES_DBNAME=agentrq

# ── Authentication ───────────────────────────────────────
AGENTRQ_AUTH_JWT_SECRET=CHANGE-ME-TO-A-LONG-RANDOM-SECRET-32-CHARS-MIN
# Encrypts MCP workspace tokens (AES-256-GCM). Must be exactly 32 bytes.
# CRITICAL: If you change this, all existing MCP tokens become unreadable.
AGENTRQ_AUTH_WORKSPACE_TOKEN_KEY=CHANGE-ME-EXACTLY-32-BYTES-LONG!
# Root login for initial setup (disable after first use)
AGENTRQ_AUTH_ROOT_LOGIN_ENABLED=true
AGENTRQ_AUTH_ROOT_ACCESS_TOKEN=CHANGE-ME-ROOT-TOKEN

# ── Google OAuth2 ────────────────────────────────────────
AGENTRQ_ACCOUNTS_OAUTH2_CLI_GOOGLE_CLIENT_ID=your-client-id.apps.googleusercontent.com
AGENTRQ_ACCOUNTS_OAUTH2_CLI_GOOGLE_CLIENT_SECRET=your-client-secret

# ── GitHub OAuth2 (optional — adds "Sign in with GitHub") ─
AGENTRQ_ACCOUNTS_OAUTH2_CLI_GITHUB_CLIENT_ID=
AGENTRQ_ACCOUNTS_OAUTH2_CLI_GITHUB_CLIENT_SECRET=

# ── SMTP (optional) ─────────────────────────────────────
AGENTRQ_SMTP_ENABLED=false
AGENTRQ_SMTP_HOST=smtp.example.com
AGENTRQ_SMTP_PORT=587
AGENTRQ_SMTP_USERNAME=user@example.com
AGENTRQ_SMTP_PASSWORD=yourpassword
AGENTRQ_SMTP_FROM=AgentRQ <no-reply@example.com>

# ── Slack (optional) ────────────────────────────────────
AGENTRQ_SLACK_ENABLED=false
AGENTRQ_SLACK_CLIENT_ID=your-slack-client-id
AGENTRQ_SLACK_CLIENT_SECRET=your-slack-client-secret
AGENTRQ_SLACK_SIGNING_SECRET=your-slack-signing-secret
AGENTRQ_SLACK_APP_ID=your-slack-app-id

# ── Web Push (optional — PWA notifications) ──────────────
# Generate with: npx web-push generate-vapid-keys
AGENTRQ_WEBPUSH_VAPID_PUBLIC_KEY=
AGENTRQ_WEBPUSH_VAPID_PRIVATE_KEY=
AGENTRQ_WEBPUSH_SUBSCRIBER=mailto:you@example.com

# ── Storage ──────────────────────────────────────────────
# Writable directory for attachments and local skill files
AGENTRQ_STORAGE_DIR=./_storage
# Attachments older than this are deleted daily at midnight UTC
AGENTRQ_APP_ATTACHMENT_RETENTION=7d
# Where skill files and attachments live: local or s3
AGENTRQ_SKILLS_STORAGE=local
AGENTRQ_ARTIFACTS_STORAGE=local

# ── S3-compatible storage (only when one of the above is s3) ─
AGENTRQ_S3_ENDPOINT=https://s3.us-east-1.amazonaws.com
AGENTRQ_S3_ACCESS_KEY=
AGENTRQ_S3_SECRET_ACCESS_KEY=
AGENTRQ_S3_REGION=us-east-1
AGENTRQ_S3_BUCKET=
# Base of public links, e.g. a CDN (default: <endpoint>/<bucket>)
AGENTRQ_S3_PUBLIC_URL=

# ── DDoS Protection ─────────────────────────────────────
AGENTRQ_DDOS_ENABLED=true
AGENTRQ_DDOS_MAX_REQ_PER_SEC=20
AGENTRQ_DDOS_BLOCK_DURATION=5m

# ── Rate Limiting ───────────────────────────────────────
AGENTRQ_RATELIMIT_ENABLED=true
AGENTRQ_RATELIMIT_MAX_PER_IP=60
AGENTRQ_RATELIMIT_MAX_PER_USER=100
AGENTRQ_RATELIMIT_WINDOW=60s

# ── Multiple instances (optional) ────────────────────────
# ID generator node, 0-1023 — must be unique per running instance
MONOFLAKE_NODE=1
# Instance identity for daemon pairing (default: node-<MONOFLAKE_NODE>)
AGENTRQ_INSTANCE_ID=
```

### 3. Create storage directory and run

```bash
mkdir -p _storage
chmod 0777 _storage
docker run -d \
    --name agentrq \
    --restart unless-stopped \
    -p 2026:2026 \
    --env-file .env \
    -v ./_storage:/_storage \
    agentrq/agentrq:latest
```

### 4. Verify

```bash
# Check container is running
docker ps --filter name=agentrq

# Check logs
docker logs agentrq

# Open in browser
open http://localhost:2026  # macOS
# or: xdg-open http://localhost:2026  # Linux
```

AgentRQ is now running at **http://localhost:2026**.

---

## Production Setup

### Differences from local dev

| Setting | Local Dev | Production |
|---|---|---|
| `AGENTRQ_BASE_URL` | `http://localhost` | `https://your-domain.com` |
| `AGENTRQ_DOMAIN` | `localhost` | `your-domain.com` |
| `AGENTRQ_SSL_ENABLED` | `false` | `true` |
| `AGENTRQ_SSL_LETSENCRYPT_EMAIL` | (empty) | `you@example.com` |
| `AGENTRQ_AUTH_ROOT_LOGIN_ENABLED` | `true` | `false` |
| `AGENTRQ_AUTH_JWT_SECRET` | any string | **long random secret (32+ chars)** |
| Database | SQLite | PostgreSQL recommended |

### Production `.env` changes

```env
# ── App ──────────────────────────────────────────────────
ENV=production
PORT=80
AGENTRQ_BASE_URL=https://your-domain.com
AGENTRQ_DOMAIN=your-domain.com

# ── TLS (built-in Let's Encrypt) ─────────────────────────
AGENTRQ_SSL_ENABLED=true
AGENTRQ_SSL_LETSENCRYPT_EMAIL=you@example.com
AGENTRQ_SSL_CACHE_DIR=/_certs
# Optional: Cloudflare DNS challenge for wildcard certs
AGENTRQ_SSL_CLOUDFLARE_API_TOKEN=

# ── Database: PostgreSQL (recommended for production) ────
AGENTRQ_SQLITE_ENABLED=false
AGENTRQ_POSTGRES_ENABLED=true
AGENTRQ_POSTGRES_HOST=your-postgres-host
AGENTRQ_POSTGRES_PORT=5432
AGENTRQ_POSTGRES_USER=postgres
AGENTRQ_POSTGRES_PASSWORD=your-secure-password
AGENTRQ_POSTGRES_DBNAME=agentrq

# ── Authentication ───────────────────────────────────────
AGENTRQ_AUTH_JWT_SECRET=USE-A-CRYPTOGRAPHICALLY-RANDOM-STRING-HERE
AGENTRQ_AUTH_ROOT_LOGIN_ENABLED=false
AGENTRQ_AUTH_ROOT_ACCESS_TOKEN=CHANGE-ME
```

### Production docker run (with TLS)

```bash
mkdir -p _storage _certs
chmod 0777 _storage _certs
docker run -d \
    --name agentrq \
    --restart unless-stopped \
    -p 80:80 -p 443:443 \
    --env-file .env \
    -v ./_storage:/_storage \
    -v ./_certs:/_certs \
    agentrq/agentrq:latest
```

> **Note:** Ports 80 and 443 must be reachable from the internet for Let's Encrypt certificate provisioning.

---

## Google OAuth2 Setup

This is required for user authentication. Follow these steps:

1. Go to [Google Cloud Console → APIs & Services → Credentials](https://console.cloud.google.com/apis/credentials)
2. Click **+ Create Credentials** → **OAuth 2.0 Client ID**
3. Application type: **Web application**
4. Add **Authorized Redirect URI**:
   - Production: `https://your-domain.com/api/v1/auth/google/callback`
   - Local dev: `http://localhost:2026/api/v1/auth/google/callback`
5. Click **Create** — copy the Client ID and Client Secret into your `.env`:
   - `AGENTRQ_ACCOUNTS_OAUTH2_CLI_GOOGLE_CLIENT_ID=<Client ID>`
   - `AGENTRQ_ACCOUNTS_OAUTH2_CLI_GOOGLE_CLIENT_SECRET=<Client Secret>`

> Google allows multiple redirect URIs per credential, so you can use the same credential for both local and production.

---

## Connecting Claude Code / MCP Clients

After setup, sign into your AgentRQ instance, create a workspace, and copy the MCP token. Then configure `.mcp.json`:

```json
{
  "mcpServers": {
    "agentrq": {
      "type": "http",
      "url": "https://WORKSPACE_ID.mcp.your-domain.com/mcp?token=YOUR_TOKEN"
    }
  }
}
```

For local dev (no TLS):

```json
{
  "mcpServers": {
    "agentrq": {
      "type": "http",
      "url": "http://WORKSPACE_ID.mcp.localhost:2026/mcp?token=YOUR_TOKEN"
    }
  }
}
```

---

## Common Operations

```bash
# Stop
docker stop agentrq

# Start (after stop)
docker start agentrq

# Restart
docker restart agentrq

# Remove container
docker stop agentrq && docker rm agentrq

# Update to latest version
docker stop agentrq && docker rm agentrq
docker pull agentrq/agentrq:latest
# Then re-run the docker run command above

# View logs (follow mode)
docker logs -f agentrq

# View last 100 lines
docker logs --tail 100 agentrq
```

---

## Database Notes

| | SQLite | PostgreSQL |
|---|---|---|
| **Setup** | Zero config, default | Requires external Postgres instance |
| **Best for** | Personal use, small teams | Multi-user teams, high concurrency |
| **Storage** | Single file at `_storage/agentrq.db` | External database server |
| **Backups** | Copy the `_storage/` directory | Use `pg_dump` or managed backups |
| **Schema** | Auto-migrated on startup | Auto-migrated on startup |

### Important

- **Attachments and skill files** are stored under `AGENTRQ_STORAGE_DIR` (`_storage/`, in `artifacts/` and `skills/`) regardless of database choice — always back up this directory, or move them to S3 with `AGENTRQ_ARTIFACTS_STORAGE=s3` / `AGENTRQ_SKILLS_STORAGE=s3`.
- **Every stored file has a public link** that needs no sign-in: `<AGENTRQ_BASE_URL>/storage/...` for local storage, or `AGENTRQ_S3_PUBLIC_URL` (default `<endpoint>/<bucket>`) for S3. With S3, the bucket must allow public reads under `skills/` and `artifacts/`.
- **`MONOFLAKE_NODE`** must be unique per running instance when you run more than one, or IDs collide.
- **`AGENTRQ_AUTH_JWT_SECRET`** signs user session JWTs. Changing it invalidates all active sessions.
- **`AGENTRQ_AUTH_WORKSPACE_TOKEN_KEY`** encrypts integrated OAuth access tokens (such as Slack bot tokens) using AES-256-GCM. Must be exactly 32 bytes. Keep this key stable across restarts.
- **Never commit `.env`** to version control — it contains secrets. Add it to `.gitignore`.

---

## Environment Variable Reference

| Variable | Required | Default | Description |
|---|---|---|---|
| `ENV` | Yes | `production` | Environment mode |
| `PORT` | Yes | `3000` | HTTP listen port |
| `AGENTRQ_BASE_URL` | Yes | — | Full public URL (e.g. `https://your-domain.com`) |
| `AGENTRQ_DOMAIN` | Yes | — | Domain without protocol (e.g. `your-domain.com`) |
| `AGENTRQ_BASE_PATH` | No | — | Sub-path behind a reverse proxy (e.g. `/agentrq`); the proxy strips it before forwarding |
| `AGENTRQ_PROXY_DOMAIN` | No | — | Public domain of the reverse proxy; requests with this Host skip strict subdomain checks |
| `AGENTRQ_COOKIE_SECURE` | No | follows `AGENTRQ_SSL_ENABLED` | Force the cookie Secure flag when a proxy terminates TLS |
| `AGENTRQ_SSL_ENABLED` | No | `false` | Enable built-in TLS via Let's Encrypt |
| `AGENTRQ_SSL_LETSENCRYPT_EMAIL` | If TLS | — | Email for Let's Encrypt registration |
| `AGENTRQ_SSL_CACHE_DIR` | No | `/_certs` | Directory for TLS certificate cache |
| `AGENTRQ_SSL_CLOUDFLARE_API_TOKEN` | No | — | Cloudflare API token for DNS challenge |
| `AGENTRQ_SQLITE_ENABLED` | No | `true` | Use SQLite as database backend |
| `AGENTRQ_SQLITE_DSN` | If SQLite | `./_storage/agentrq.db` | Path to SQLite database file |
| `AGENTRQ_POSTGRES_ENABLED` | No | `false` | Use PostgreSQL as database backend |
| `AGENTRQ_POSTGRES_HOST` | If Postgres | — | PostgreSQL host |
| `AGENTRQ_POSTGRES_PORT` | If Postgres | `5432` | PostgreSQL port |
| `AGENTRQ_POSTGRES_USER` | If Postgres | — | PostgreSQL user |
| `AGENTRQ_POSTGRES_PASSWORD` | If Postgres | — | PostgreSQL password |
| `AGENTRQ_POSTGRES_DBNAME` | If Postgres | `agentrq` | PostgreSQL database name |
| `AGENTRQ_AUTH_JWT_SECRET` | Yes | — | Secret for signing session JWTs (32+ chars) |
| `AGENTRQ_AUTH_WORKSPACE_TOKEN_KEY` | Yes | — | AES-256-GCM encryption key for integration tokens (e.g. Slack tokens, exactly 32 bytes) |
| `AGENTRQ_AUTH_ROOT_LOGIN_ENABLED` | No | `false` | Enable root login bypass for initial setup |
| `AGENTRQ_AUTH_ROOT_ACCESS_TOKEN` | If root | — | Root login token |
| `AGENTRQ_ACCOUNTS_OAUTH2_CLI_GOOGLE_CLIENT_ID` | Yes | — | Google OAuth2 Client ID |
| `AGENTRQ_ACCOUNTS_OAUTH2_CLI_GOOGLE_CLIENT_SECRET` | Yes | — | Google OAuth2 Client Secret |
| `AGENTRQ_ACCOUNTS_OAUTH2_CLI_GITHUB_CLIENT_ID` | No | — | GitHub OAuth2 Client ID; empty hides "Sign in with GitHub" |
| `AGENTRQ_ACCOUNTS_OAUTH2_CLI_GITHUB_CLIENT_SECRET` | If GitHub | — | GitHub OAuth2 Client Secret |
| `AGENTRQ_SMTP_ENABLED` | No | `false` | Enable email notifications |
| `AGENTRQ_SMTP_HOST` | If SMTP | — | SMTP server host |
| `AGENTRQ_SMTP_PORT` | If SMTP | `587` | SMTP server port |
| `AGENTRQ_SMTP_USERNAME` | If SMTP | — | SMTP username |
| `AGENTRQ_SMTP_PASSWORD` | If SMTP | — | SMTP password |
| `AGENTRQ_SMTP_FROM` | If SMTP | — | From address for emails |
| `AGENTRQ_SLACK_ENABLED` | No | `false` | Enable Slack integration |
| `AGENTRQ_SLACK_CLIENT_ID` | If Slack | — | Slack app Client ID |
| `AGENTRQ_SLACK_CLIENT_SECRET` | If Slack | — | Slack app Client Secret |
| `AGENTRQ_SLACK_SIGNING_SECRET` | If Slack | — | Slack app Signing Secret |
| `AGENTRQ_SLACK_APP_ID` | If Slack | — | Slack App ID |
| `AGENTRQ_WEBPUSH_VAPID_PUBLIC_KEY` | No | — | VAPID public key for PWA push notifications |
| `AGENTRQ_WEBPUSH_VAPID_PRIVATE_KEY` | If Web Push | — | VAPID private key |
| `AGENTRQ_WEBPUSH_SUBSCRIBER` | No | `mailto:hi@example.com` | Contact URI (`mailto:` or `https:`) shown to push services |
| `AGENTRQ_STORAGE_DIR` | No | `./_storage` | Writable directory for attachments and local skill files |
| `AGENTRQ_APP_ATTACHMENT_RETENTION` | No | `7d` | Delete attachments older than this, daily at midnight UTC (`7d`, `168h`) |
| `AGENTRQ_SKILLS_STORAGE` | No | `local` | Where skill files are kept: `local` or `s3` |
| `AGENTRQ_ARTIFACTS_STORAGE` | No | `local` | Where attachments are kept: `local` or `s3` |
| `AGENTRQ_S3_ENDPOINT` | If S3 | — | S3-compatible endpoint (AWS S3, MinIO, R2), path-style |
| `AGENTRQ_S3_ACCESS_KEY` | If S3 | — | S3 access key |
| `AGENTRQ_S3_SECRET_ACCESS_KEY` | If S3 | — | S3 secret key |
| `AGENTRQ_S3_REGION` | No | `us-east-1` | S3 region |
| `AGENTRQ_S3_BUCKET` | If S3 | — | Existing bucket name |
| `AGENTRQ_S3_PUBLIC_URL` | No | `<endpoint>/<bucket>` | Base of public file links, e.g. a CDN |
| `AGENTRQ_DDOS_ENABLED` | No | `true` | Enable DDoS protection |
| `AGENTRQ_DDOS_MAX_REQ_PER_SEC` | No | `20` | Max requests per second before blocking |
| `AGENTRQ_DDOS_BLOCK_DURATION` | No | `5m` | How long to block offending IPs |
| `AGENTRQ_RATELIMIT_ENABLED` | No | `true` | Enable rate limiting |
| `AGENTRQ_RATELIMIT_MAX_PER_IP` | No | `60` | Max requests per window per IP |
| `AGENTRQ_RATELIMIT_MAX_PER_USER` | No | `100` | Max requests per window per user |
| `AGENTRQ_RATELIMIT_WINDOW` | No | `60s` | Rate limit window duration |
| `MONOFLAKE_NODE` | No | `1` | ID generator node (0–1023); must be unique per running instance |
| `AGENTRQ_INSTANCE_ID` | No | `node-<MONOFLAKE_NODE>` | Instance identity for daemon pairing |
