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

# Docker Setup

> Complete guide to deploying GAIA with Docker Compose

## Docker Deployment Guide

GAIA uses Docker Compose to orchestrate all the required services. This makes deployment simple and ensures consistency across different environments.

<Note>
  **Looking for an easier setup?** The [GAIA CLI](/cli/installation) provides a guided wizard that handles Docker setup automatically, including prerequisite checks, environment variable discovery, and service management. This guide covers manual Docker setup for advanced users who prefer full control.
</Note>

## Prerequisites

<AccordionGroup>
  <Accordion icon="docker" title="Docker Installation">
    **Install Docker Desktop** (recommended for beginners):

    * [Download for macOS](https://docs.docker.com/desktop/install/mac-install/)
    * [Download for Windows](https://docs.docker.com/desktop/install/windows-install/)
    * [Download for Linux](https://docs.docker.com/desktop/install/linux-install/)

    **Or install Docker Engine** (for servers):

    ```bash theme={null}
    # Ubuntu/Debian (example)
    sudo apt-get update
    sudo apt-get install -y docker.io docker-compose-plugin
    sudo systemctl enable --now docker

    # Add user to docker group
    sudo usermod -aG docker $USER
    newgrp docker
    ```
  </Accordion>

  <Accordion icon="server" title="System Requirements">
    **Minimum Requirements:**

    * 2 CPU cores
    * 4GB RAM
    * 10GB free disk space
    * Docker Engine 20.10+
    * Docker Compose v2.0+

    **Recommended for Production:**

    * 4+ CPU cores
    * 8+ GB RAM
    * 50+ GB SSD storage
    * Regular backups configured
  </Accordion>
</AccordionGroup>

## Project Structure

<Note>
  When using the CLI quick start, running `gaia init` with **Self-Host (Docker)**
  scaffolds this structure automatically.
</Note>

```
gaia/
├── apps/
│   ├── api/
│   │   ├── Dockerfile
│   │   ├── .env                # API environment variables
│   │   └── ...
│   ├── web/
│   │   ├── Dockerfile
│   │   ├── .env                # Web environment variables
│   │   └── ...
├── docs/
├── infra/
│   └── docker/
│       ├── docker-compose.yml      # Development compose file
│       └── docker-compose.prod.yml # Production compose file
└── ...
```

## Quick Start

<Steps>
  <Step title="Install CLI and initialize self-host mode">
    <CodeGroup>
      ```bash npm theme={null}
      npm install -g @heygaia/cli
      ```

      ```bash pnpm theme={null}
      pnpm add -g @heygaia/cli
      ```

      ```bash bun theme={null}
      bun add -g @heygaia/cli
      ```
    </CodeGroup>

    ```bash theme={null}
    gaia init
    ```

    Choose **Self-Host (Docker)** in the setup wizard.
  </Step>

  <Step title="Start the stack">
    ```bash theme={null}
    gaia start
    ```
  </Step>

  <Step title="Verify and monitor">
    ```bash theme={null}
    gaia status
    gaia logs
    ```

    Access the applications:

    * **GAIA Web**: [http://localhost:3000](http://localhost:3000)
    * **Backend API**: [http://localhost:8000](http://localhost:8000)
    * **API Docs**: [http://localhost:8000/docs](http://localhost:8000/docs)
    * **ChromaDB**: [http://localhost:8080](http://localhost:8080)
    * **RabbitMQ Management**: [http://localhost:15672](http://localhost:15672) (guest/guest)
    * **Mongo Express**: [http://localhost:8081](http://localhost:8081) (admin/password)
  </Step>
</Steps>

## Service Overview

The Docker Compose setup includes the following services:

<AccordionGroup>
  <Accordion icon="react" title="Web (gaia-web)">
    **Next.js React Application**

    * Port: 3000
    * Hot reload enabled in development
    * Serves the web interface

    ```yaml theme={null}
    web:
      container_name: gaia-web
      build: ./apps/web
      ports:
        - "3000:3000"
      environment:
        - NEXT_PUBLIC_API_BASE_URL=http://localhost:8000
    ```
  </Accordion>

  <Accordion icon="server" title="Backend (gaia-backend)">
    **FastAPI Python Application**

    * Port: 8000
    * Auto-reload enabled in development
    * RESTful API and WebSocket support

    ```yaml theme={null}
    gaia-backend:
      container_name: gaia-backend
      build: ./apps/api
      ports:
        - "8000:80"
      command: ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--reload"]
    ```
  </Accordion>

  <Accordion icon="database" title="Databases">
    **PostgreSQL** (Port: 5432)

    * Stores user data and application state
    * User: postgres, Password: postgres, DB: langgraph

    **MongoDB** (Port: 27017)

    * Document storage and metadata
    * No authentication in development

    **Redis** (Port: 6379)

    * Caching and session storage
    * No authentication in development

    **ChromaDB** (Port: 8080)

    * Vector database for embeddings
    * Persistent storage in Docker volume
  </Accordion>

  <Accordion title="Background Workers">
    **Worker** - Handles scheduled tasks and background processing
    **ARQ Worker** - Async task queue processing
    **RabbitMQ** - Message broker (Port: 5672, Management: 15672)
  </Accordion>
</AccordionGroup>

## Persistent Workspace Storage (JuiceFS and FUSE)

GAIA's per-user workspace (file uploads, agent artifacts, installed skills) is backed by [JuiceFS](https://juicefs.com), a filesystem that stores file **data** in object storage (Cloudflare R2, S3, or any S3-compatible store) and file **metadata** in a database. The backend mounts it at `/mnt/jfs` and reads and writes files there like a local disk.

JuiceFS mounts through **FUSE** (Filesystem in Userspace), a Linux kernel feature. This has direct consequences for where and how you can run the backend.

### Run the backend in a container

Because the mount needs Linux FUSE, run the backend in a container on every platform:

| Host OS | Native (`uvicorn` on the host)                                | Containerized (Docker)                                |
| ------- | ------------------------------------------------------------- | ----------------------------------------------------- |
| Linux   | Works if `juicefs` is installed and `/dev/fuse` is accessible | Works                                                 |
| macOS   | Not supported (no Linux FUSE)                                 | Works, Docker Desktop's Linux VM provides `/dev/fuse` |
| Windows | Not supported (no Linux FUSE)                                 | Works, Docker Desktop / WSL2 provides `/dev/fuse`     |

The self-host Compose stack already runs the backend in a container, so all three platforms are covered. The native path is a development-only convenience on Linux.

<Note>
  Run the backend in a container even on macOS and Windows. Docker Desktop runs a Linux VM that exposes `/dev/fuse`, so the FUSE mount works inside the container even though the host OS cannot mount it directly.
</Note>

### How the mount works

FUSE lets a userspace program act as a filesystem instead of the kernel:

1. The kernel exposes a device at `/dev/fuse`.
2. The `juicefs mount` process opens that device and attaches itself as the driver for `/mnt/jfs`.
3. When the backend reads or writes a path under `/mnt/jfs`, the kernel routes the call to the JuiceFS process.
4. JuiceFS looks up metadata in its database, fetches or stores the file's chunks in object storage, and serves the result back through `/dev/fuse`.

The backend never sees any of this, it just reads and writes files under `/mnt/jfs`.

### Container settings that make it work

A Docker container is unprivileged by default: it cannot mount filesystems, cannot see host devices, and runs under an AppArmor profile that blocks `mount`. The Compose file grants exactly what FUSE needs and nothing more:

```yaml theme={null}
gaia-backend:
  environment:
    # Where the backend expects the mount. Must match the mountpoint.
    JUICEFS_HOST_MOUNT_PATH: /mnt/jfs
    # FUSE filesystems can't be watched with inotify, so artifact
    # detection tails the JuiceFS access log instead.
    ARTIFACT_DETECTION_MODE: accesslog
  cap_add:
    - SYS_ADMIN # the mount() syscall requires this capability
  devices:
    - /dev/fuse:/dev/fuse # pass the FUSE device into the container
  security_opt:
    - apparmor:unconfined # lift the default profile that denies mount
  volumes:
    - juicefs_cache:/var/cache/juicefs # keep the local chunk cache warm
```

<AccordionGroup>
  <Accordion icon="key" title="cap_add: SYS_ADMIN">
    The `mount(2)` syscall requires the `CAP_SYS_ADMIN` capability, which Docker drops by default. Without it, `juicefs` cannot perform the mount and fails before it starts. This grants only that one capability, not full `privileged` access.
  </Accordion>

  <Accordion icon="microchip" title="devices: /dev/fuse">
    The JuiceFS process opens `/dev/fuse` to talk to the kernel's FUSE module. Containers see no host devices by default, so this passes the device through. Without it you get `fusermount3: cannot open /dev/fuse: No such file or directory`.
  </Accordion>

  <Accordion icon="shield" title="security_opt: apparmor:unconfined">
    On Ubuntu and Debian hosts, Docker applies a default AppArmor profile that denies the `mount` operation even when the container holds `CAP_SYS_ADMIN`. This lifts that profile. On hosts without AppArmor it has no effect.
  </Accordion>

  <Accordion icon="database" title="volumes: juicefs_cache">
    JuiceFS keeps a local disk cache of file chunks so repeat reads don't go back to object storage. Persisting it in a named volume keeps the cache warm across restarts. This affects speed only, not correctness.
  </Accordion>
</AccordionGroup>

<Warning>
  Avoid `privileged: true` as a shortcut. It grants every capability and device to the container, which is a much larger attack surface. The four scoped settings above are the least-privilege way to enable a single FUSE mount.
</Warning>

### Required configuration

The container settings make a mount *possible*. JuiceFS still needs an object store and a metadata database before it will mount. Set these in `apps/api/.env`:

| Variable                                                       | Purpose                                                               |
| -------------------------------------------------------------- | --------------------------------------------------------------------- |
| `R2_ACCOUNT_ID`, `R2_BUCKET`, `R2_ACCESS_KEY`, `R2_SECRET_KEY` | Cloudflare R2 (or S3-compatible) object store for file data           |
| `JUICEFS_META_URL_TEMPLATE`                                    | Connection URL for the metadata database (a PostgreSQL DB works well) |

Until both are configured, the backend starts normally and chat works, but workspace features (uploads, artifacts, skill installs) degrade and log `JuiceFSUnavailable`. See [Environment Variables](/configuration/environment-variables) for the full list.

## Docker Commands

### Starting and Stopping

```bash theme={null}
# Start all services
docker compose up -d

# Start specific services
docker compose up -d gaia-backend postgres redis

# Stop all services
docker compose down

# Stop and remove volumes (⚠️ deletes data)
docker compose down -v
```

### Viewing Logs

```bash theme={null}
# View logs for all services
docker compose logs -f

# View logs for specific service
docker compose logs -f gaia-backend

# View logs with timestamps
docker compose logs -f -t

# View last 100 lines
docker compose logs --tail=100 gaia-backend
```

### Service Management

```bash theme={null}
# Restart a service
docker compose restart gaia-backend

# Rebuild and restart a service
docker compose up -d --build gaia-backend

# Execute commands in running container
docker compose exec gaia-backend bash
docker compose exec postgres psql -U postgres -d langgraph

# Check service status
docker compose ps
```

## Development Mode

For development, use the default `docker-compose.yml`:

```bash theme={null}
# Start with hot reload
docker compose up -d

# View real-time logs
docker compose logs -f gaia-backend frontend
```

**Development Features:**

* Hot reload for both frontend and backend
* Source code mounted as volumes
* Debug logging enabled
* Development databases with default credentials

## Health Checks

All services include health checks. Monitor service health:

```bash theme={null}
# Check health status
docker compose ps

# Services with health checks:
# ✓ gaia-backend - HTTP health endpoint
# ✓ postgres - pg_isready
# ✓ redis - redis-cli ping
# ✓ mongo - mongosh ping
# ✓ chromadb - TCP connection check
# ✓ rabbitmq - rabbitmqctl status
```

## Data Persistence

Data is persisted using Docker volumes:

```bash theme={null}
# View volumes
docker volume ls | grep gaia

# Backup a volume
docker run --rm -v gaia-dev_pgdata:/data -v $(pwd):/backup alpine tar czf /backup/postgres-backup.tar.gz -C /data .

# Restore a volume
docker run --rm -v gaia-dev_pgdata:/data -v $(pwd):/backup alpine tar xzf /backup/postgres-backup.tar.gz -C /data
```

**Volume Locations:**

* `pgdata` - PostgreSQL data
* `mongo_data` - MongoDB data
* `redis_data` - Redis data
* `chroma_data` - ChromaDB vectors
* `rabbitmq_data` - RabbitMQ messages

## Networking

Services communicate through the `gaia_network` bridge network:

```bash theme={null}
# Inspect network
docker network inspect gaia-dev_gaia_network

# Services can reach each other by container name:
# - gaia-backend → postgres:5432
# - gaia-backend → redis:6379
# - gaia-backend → chromadb:8000
```

## Troubleshooting

<AccordionGroup>
  <Accordion icon="triangle-exclamation" title="Logs show JuiceFSUnavailable">
    The backend can't reach the workspace mount. Check, in order:

    * **Object store and metadata are configured**: `R2_*` and `JUICEFS_META_URL_TEMPLATE` must be set in `apps/api/.env`. Without them JuiceFS never mounts.
    * **FUSE settings are present**, confirm `cap_add: SYS_ADMIN`, `devices: /dev/fuse`, and `security_opt: apparmor:unconfined` are on `gaia-backend` and `arq_worker`.
    * **The mount came up**, run `docker compose exec gaia-backend mountpoint /mnt/jfs`. A non-zero result means the mount failed; check `docker compose logs gaia-backend` for the JuiceFS error.
  </Accordion>

  <Accordion icon="microchip" title="fusermount3: cannot open /dev/fuse">
    The FUSE device isn't reaching the container. Confirm the `devices: /dev/fuse:/dev/fuse` mapping is present. On a Linux host, verify the device exists with `ls -l /dev/fuse` and load the module if missing: `sudo modprobe fuse`. On macOS and Windows, make sure Docker Desktop is running (it provides the device from its Linux VM).
  </Accordion>

  <Accordion icon="lock" title="Mount fails with permission denied">
    The container holds the capability but the host's security profile is blocking the mount. Confirm `security_opt: apparmor:unconfined` is set. On SELinux hosts, you may also need to allow the mount in your local policy.
  </Accordion>
</AccordionGroup>

## Alternative: CLI Setup

For a simplified setup experience with automatic configuration, see the [CLI Setup Guide](/self-hosting/cli-setup). The CLI handles all the Docker commands, environment setup, and health checks automatically.

## Next Steps

<CardGroup cols={1}>
  <Card title="Environment Variables" icon="gear" href="/configuration/environment-variables">
    Configure your API keys and settings
  </Card>
</CardGroup>
