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

# Quickstart Guide

> Deploy CaseBender locally in minutes

<Tip>
  **Looking for an easier way?** Use our [Desktop Installer](/en/deployment/desktop-installer) for one-click deployment with automatic configuration. No command line required!
</Tip>

## Prerequisites

Before you begin, make sure you have the following installed on your system:

* Docker Engine (20.10.0 or higher)
* Docker Compose (v2.0.0 or higher)
* OpenSSL (for generating SSL certificates)

### Installing Docker

#### For macOS:

1. Download and install Docker Desktop from [Docker Hub](https://hub.docker.com/editions/community/docker-ce-desktop-mac)
2. Follow the installation wizard
3. Verify installation:

```bash theme={null}
docker --version
docker compose --version
```

#### For Linux (Ubuntu/Debian):

```bash theme={null}
# Update package index
sudo apt-get update

# Install prerequisites
sudo apt-get install \
    apt-transport-https \
    ca-certificates \
    curl \
    gnupg \
    lsb-release

# Add Docker's official GPG key
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg

# Set up stable repository
echo \
  "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \
  $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

# Install Docker Engine
sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-compose-plugin

# Add your user to docker group
sudo usermod -aG docker $USER
```

#### For Windows:

1. Download and install Docker Desktop from [Docker Hub](https://hub.docker.com/editions/community/docker-ce-desktop-windows)
2. Enable WSL 2 following Docker's documentation
3. Follow the installation wizard
4. Verify installation in PowerShell:

```powershell theme={null}
docker --version
docker compose --version
```

## Step 1: Create Project Directory

Create a new directory for your CaseBender deployment and navigate into it:

```bash theme={null}
mkdir casebender-deployment
cd casebender-deployment
```

## Step 2: Configure Environment Variables

Create a `.env` file with the following content:

```bash theme={null}
# Authentication
AUTH_SECRET="B7OawnWf6+LwB/9yXbxbk4ppZ1khydqj4qc9k9g3nnE="
AUTH_SALT="B7OawnW"
AUTH_TRUST_HOST=true
NEXTAUTH_URL=https://local.casebender.com
NEXTAPP_URL=https://local.casebender.com

# License (optional)
# CaseBender auto-generates and persists a license secret key on first boot
# (stored in the "casebender_secret" volume) and reuses it across restarts and
# updates — no action required. Only set LICENSE_SECRET_KEY if you want to manage
# the key yourself: back it up, rotate it, or share it across replicas.
# Generate one with: openssl rand -hex 32
# LICENSE_SECRET_KEY=

# Liveblocks Configuration (real-time collaboration)
LIVEBLOCKS_SECRET_KEY=sk_dev_HaIczPV4gPit5_gx7YRsNXLGJNzBE5wQ8z8I1H8ft3ZPZHVrfH2ryJJ586ezHYla

# Database Configuration (host is the internal "db" service)
POSTGRES_PASSWORD=88AlwaysTimeToWin88
POSTGRES_PRISMA_URL="postgresql://superadmin:88AlwaysTimeToWin88@db:5432/casebender?schema=public"
POSTGRES_URL="postgresql://superadmin:88AlwaysTimeToWin88@db:5432/casebender?schema=public"
POSTGRES_URL_NON_POOLING="postgresql://superadmin:88AlwaysTimeToWin88@db:5432/casebender?schema=public"

# Redis Configuration
REDIS_URL="redis://redis:6379"
# Discrete host/port fallback — read by every service (also covers older
# images that don't parse REDIS_URL for the background job queue).
REDIS_HOST=redis
REDIS_PORT=6379
```

<Note>
  CaseBender runs as several containers: the **web app** (`app`), a **REST API gateway**
  (`api`), an **alert ingestion gateway** (`ingestion`, this is what receives integration
  webhooks such as Microsoft Defender), a background **worker**, and the **workflow** and
  **MISP** processors. Nginx sits in front and routes traffic to the right service. All of these
  are included in the Compose file below.
</Note>

## Step 3: Generate SSL Certificates

For local development, generate self-signed SSL certificates:

```bash theme={null}
# Generate SSL certificate and key
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
  -keyout local-casebender.key \
  -out local-casebender.crt \
  -subj "/CN=local.casebender.com/O=CaseBender/C=US"

# Verify the certificate
openssl x509 -in local-casebender.crt -text -noout
```

## Step 4: Configure Nginx

Create `nginx.conf` with the following content:

```nginx theme={null}
events {
    worker_connections 1024;
}

http {
    # ─── Upstreams ─────────────────────────────────────────────
    upstream web_app {
        server app:3000;
    }

    upstream api_service {
        server api:3005;
    }

    upstream ingestion_service {
        server ingestion:3003;
    }

    # ─── Rate Limiting ─────────────────────────────────────────
    limit_req_zone $binary_remote_addr zone=api_limit:10m rate=100r/s;
    limit_req_zone $binary_remote_addr zone=ingest_limit:10m rate=1000r/s;

    # ─── HTTP -> HTTPS Redirect ────────────────────────────────
    server {
        listen 80;
        server_name local.casebender.com;
        return 301 https://$server_name$request_uri;
    }

    # ─── Main HTTPS Server ─────────────────────────────────────
    server {
        listen 443 ssl;
        http2 on;
        server_name local.casebender.com;

        ssl_certificate /etc/nginx/certs/local-casebender.crt;
        ssl_certificate_key /etc/nginx/certs/local-casebender.key;

        ssl_protocols TLSv1.2 TLSv1.3;
        client_max_body_size 100M;

        # ─── Security Headers ─────────────────────────────────
        add_header X-Frame-Options "SAMEORIGIN" always;
        add_header X-Content-Type-Options "nosniff" always;
        add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

        # ─── Ingestion API (integration webhooks) ─────────────
        location /api/v1/ingest/ {
            limit_req zone=ingest_limit burst=500 nodelay;
            proxy_pass http://ingestion_service;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }

        # Legacy ingestion route
        location /api/ingest/ {
            limit_req zone=ingest_limit burst=500 nodelay;
            proxy_pass http://ingestion_service;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }

        # ─── REST API Gateway ─────────────────────────────────
        location /api/v1/ {
            limit_req zone=api_limit burst=50 nodelay;
            proxy_pass http://api_service;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }

        location /api/openapi.json {
            proxy_pass http://api_service;
            proxy_set_header Host $host;
        }

        # ─── Health Check ─────────────────────────────────────
        location /health {
            access_log off;
            return 200 "healthy";
            add_header Content-Type text/plain;
        }

        # ─── Web Application (catch-all) ──────────────────────
        location / {
            proxy_pass http://web_app;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_set_header X-Forwarded-Host $host;
            proxy_set_header X-Forwarded-Port $server_port;

            # WebSocket support
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";

            proxy_connect_timeout 60s;
            proxy_send_timeout 60s;
            proxy_read_timeout 60s;
        }
    }
}
```

## Step 5: Create Docker Compose Configuration

Create `docker-compose.yml` with the following content:

```yaml theme={null}
services:
  # ─── Core Services ───────────────────────────────────────────
  app:
    image: casebender/casebender:latest
    platform: linux/amd64
    ports:
      - "3000:3000"
    environment:
      - AUTH_SECRET=${AUTH_SECRET}
      - AUTH_SALT=${AUTH_SALT}
      - AUTH_TRUST_HOST=true
      - NEXTAUTH_URL=${NEXTAUTH_URL:-https://local.casebender.com}
      - NEXTAPP_URL=${NEXTAPP_URL:-https://local.casebender.com}
      - LICENSE_SECRET_KEY=${LICENSE_SECRET_KEY}
      - LIVEBLOCKS_SECRET_KEY=${LIVEBLOCKS_SECRET_KEY}
      - POSTGRES_PRISMA_URL=${POSTGRES_PRISMA_URL}
      - POSTGRES_URL=${POSTGRES_URL}
      - POSTGRES_URL_NON_POOLING=${POSTGRES_URL_NON_POOLING}
      - REDIS_URL=${REDIS_URL:-redis://redis:6379}
      - REDIS_HOST=${REDIS_HOST:-redis}
      - REDIS_PORT=${REDIS_PORT:-6379}
    volumes:
      # Persists the auto-generated license secret + blob across image updates
      # so `docker compose pull` never regenerates the license.
      - casebender_secret:/app/apps/web/app/secret
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy
    restart: unless-stopped
    healthcheck:
      test: ["CMD-SHELL", "node -e \"require('http').get('http://localhost:3000/api/health', (r) => process.exit(r.statusCode === 200 ? 0 : 1)).on('error', () => process.exit(1))\""]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 60s

  api:
    image: casebender/api:latest
    platform: linux/amd64
    ports:
      - "3005:3005"
    environment:
      - PORT=3005
      - POSTGRES_PRISMA_URL=${POSTGRES_PRISMA_URL}
      - POSTGRES_URL=${POSTGRES_URL}
      - POSTGRES_URL_NON_POOLING=${POSTGRES_URL_NON_POOLING}
      - REDIS_URL=${REDIS_URL:-redis://redis:6379}
      - REDIS_HOST=${REDIS_HOST:-redis}
      - REDIS_PORT=${REDIS_PORT:-6379}
      - AUTH_SECRET=${AUTH_SECRET}
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy
    restart: unless-stopped

  ingestion:
    image: casebender/ingestion:latest
    platform: linux/amd64
    ports:
      - "3003:3003"
    environment:
      - PORT=3003
      - POSTGRES_PRISMA_URL=${POSTGRES_PRISMA_URL}
      - POSTGRES_URL=${POSTGRES_URL}
      - POSTGRES_URL_NON_POOLING=${POSTGRES_URL_NON_POOLING}
      - REDIS_URL=${REDIS_URL:-redis://redis:6379}
      - REDIS_HOST=${REDIS_HOST:-redis}
      - REDIS_PORT=${REDIS_PORT:-6379}
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy
    restart: unless-stopped

  # ─── Processing Services ─────────────────────────────────────
  worker:
    image: casebender/worker:latest
    platform: linux/amd64
    ports:
      - "3002:3002"
    environment:
      - PORT=3002
      - POSTGRES_PRISMA_URL=${POSTGRES_PRISMA_URL}
      - POSTGRES_URL=${POSTGRES_URL}
      - POSTGRES_URL_NON_POOLING=${POSTGRES_URL_NON_POOLING}
      - REDIS_URL=${REDIS_URL:-redis://redis:6379}
      - REDIS_HOST=${REDIS_HOST:-redis}
      - REDIS_PORT=${REDIS_PORT:-6379}
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy
    restart: unless-stopped

  workflow-processor:
    image: casebender/workflow-processor:latest
    platform: linux/amd64
    ports:
      - "3001:3001"
    environment:
      - POSTGRES_PRISMA_URL=${POSTGRES_PRISMA_URL}
      - POSTGRES_URL=${POSTGRES_URL}
      - POSTGRES_URL_NON_POOLING=${POSTGRES_URL_NON_POOLING}
      - REDIS_URL=${REDIS_URL:-redis://redis:6379}
      - REDIS_HOST=${REDIS_HOST:-redis}
      - REDIS_PORT=${REDIS_PORT:-6379}
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy
      app:
        condition: service_healthy
    restart: unless-stopped

  misp-processor:
    image: casebender/misp-processor:latest
    platform: linux/amd64
    ports:
      - "3004:3004"
    environment:
      - PORT=3004
      - POSTGRES_PRISMA_URL=${POSTGRES_PRISMA_URL}
      - POSTGRES_URL=${POSTGRES_URL}
      - POSTGRES_URL_NON_POOLING=${POSTGRES_URL_NON_POOLING}
      - REDIS_URL=${REDIS_URL:-redis://redis:6379}
      - REDIS_HOST=${REDIS_HOST:-redis}
      - REDIS_PORT=${REDIS_PORT:-6379}
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy
      app:
        condition: service_healthy
    restart: unless-stopped

  # ─── Infrastructure ──────────────────────────────────────────
  db:
    image: postgres:17
    environment:
      POSTGRES_USER: superadmin
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-88AlwaysTimeToWin88}
      POSTGRES_DB: casebender
    ports:
      - "5433:5432"
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U superadmin -d casebender"]
      interval: 5s
      timeout: 5s
      retries: 10
      start_period: 30s
    restart: unless-stopped

  redis:
    image: redis:7.2-alpine
    command: redis-server --protected-mode no
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 3s
      retries: 5
    restart: unless-stopped

  minio:
    image: quay.io/minio/minio
    mem_limit: 512m
    command: ["minio", "server", "/data", "--console-address", ":9090"]
    environment:
      - MINIO_ROOT_USER=minioadmin
      - MINIO_ROOT_PASSWORD=minioadmin
    ports:
      - "9090:9090"
      - "9000:9000"
    volumes:
      - "miniodata:/data"
    restart: unless-stopped

  # ─── Reverse Proxy ───────────────────────────────────────────
  nginx:
    image: nginx:alpine
    ports:
      - "443:443"
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - ./local-casebender.crt:/etc/nginx/certs/local-casebender.crt:ro
      - ./local-casebender.key:/etc/nginx/certs/local-casebender.key:ro
    depends_on:
      app:
        condition: service_healthy
    restart: unless-stopped

volumes:
  miniodata:
  pgdata:
  redis_data:
  casebender_secret:
```

<Tip>
  Need enterprise search (OpenSearch)? The Desktop Installer can add the `search-sync`,
  `opensearch`, and `opensearch-dashboards` services for you. For most local evaluations the
  default PostgreSQL-based search above is all you need.
</Tip>

## Step 6: Configure Local DNS

Add the following entry to your hosts file:

### For macOS and Linux:

```bash theme={null}
sudo echo "127.0.0.1 local.casebender.com" >> /etc/hosts
```

### For Windows:

Add the following line to `C:\Windows\System32\drivers\etc\hosts`:

```
127.0.0.1 local.casebender.com
```

## Step 7: Start the Application

1. Pull the required images:

```bash theme={null}
docker compose pull
```

2. Start all services:

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

3. Monitor the logs:

```bash theme={null}
docker compose logs -f
```

4. Access the application at `https://local.casebender.com`

## Default Login Credentials

After deploying CaseBender, you can log in with the following default credentials:

```
Username: admin@casebender.app
Password: secret1234
```

<Warning>
  For security reasons, we strongly recommend changing these default credentials immediately after your first login.
</Warning>

## Running behind a corporate proxy

If your host can only reach the internet through a corporate HTTP proxy
("proxy-only" networks), CaseBender's outbound calls — license activation and
integrations such as Microsoft Defender and Splunk — must be routed through it.

Add the proxy variables to your `.env`:

```bash theme={null}
HTTP_PROXY=http://proxy.example.com:8080
HTTPS_PROXY=http://proxy.example.com:8080
# Internal Docker services (and any on-prem targets) MUST bypass the proxy:
NO_PROXY=127.0.0.1,localhost,db,redis,minio,api,ingestion,worker,workflow-processor,misp-processor,search-sync,opensearch
```

Then pass them to every CaseBender service by adding these lines to each
service's `environment:` block in `docker-compose.yml`:

```yaml theme={null}
      - HTTP_PROXY=${HTTP_PROXY:-}
      - HTTPS_PROXY=${HTTPS_PROXY:-}
      - NO_PROXY=${NO_PROXY:-}
```

<Warning>
  `NO_PROXY` must include the internal Docker service names above. Otherwise
  container-to-container traffic (e.g. the web app calling the ingestion service)
  is sent to the corporate proxy and fails — breaking the app even though it has
  nothing to do with the internet.
</Warning>

<Note>
  **Which targets go through the proxy?** Public endpoints must go through the proxy
  and must NOT be in `NO_PROXY` — e.g. Microsoft Defender
  (`login.microsoftonline.com`, `graph.microsoft.com`). On-prem targets should be
  listed in `NO_PROXY` so they are reached directly — e.g. an internal **Splunk
  HEC**. If you use Splunk Cloud, leave it out so it routes through the proxy.
</Note>

Verify connectivity from inside a container (uses `node`, no `curl` needed):

```bash theme={null}
docker compose exec app node -e "fetch('https://login.microsoftonline.com/',{signal:AbortSignal.timeout(5000)}).then(r=>console.log('OK',r.status)).catch(e=>console.log('FAIL',e.message))"
```

When a proxy is configured, each service logs `[proxy] Outbound fetch routed through corporate proxy (...)` on startup.

## Troubleshooting

### Common Issues

1. **Certificate Warnings**:

   * The browser will show a security warning because we're using a self-signed certificate
   * Click "Advanced" and proceed to the website
   * For development purposes, this is expected and safe

2. **Port Conflicts**:

   * Ensure ports 80, 443, 3000, 3001, 3002, 3003, 3004, 3005, 5433, 6379, 9000, and 9090 are not in use
   * If needed, modify the port mappings in docker-compose.yml

3. **Database Connection**:

   * Check PostgreSQL logs: `docker compose logs db`
   * Verify database credentials in .env
   * Ensure the database is running: `docker compose ps db`

4. **Service Dependencies**:
   * If services fail to start, check their dependencies:
   ```bash theme={null}
   docker compose ps
   docker compose logs [service_name]
   ```

### Checking Logs

View logs for specific services:

```bash theme={null}
# All services
docker compose logs

# Specific service
docker compose logs [service_name]

# Follow logs
docker compose logs -f [service_name]
```

### Service Management

```bash theme={null}
# Restart a specific service
docker compose restart [service_name]

# Stop all services
docker compose down

# Remove volumes (will delete all data)
docker compose down -v
```

## Next Steps

Now that you have CaseBender running locally, you might want to:

<CardGroup cols={2}>
  <Card title="Production Deployment" icon="server" href="/deployment/overview">
    Deploy CaseBender to your production environment
  </Card>

  <Card title="Configuration" icon="gear" href="/essentials/configuration">
    Learn about advanced configuration options
  </Card>
</CardGroup>
