Skip to main content

Docker Deployment Guide

GAIA uses Docker Compose to orchestrate all the required services. This makes deployment simple and ensures consistency across different environments.
Looking for an easier setup? The GAIA CLI 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.

Prerequisites

Install Docker Desktop (recommended for beginners):Or install Docker Engine (for servers):
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

Project Structure

When using the CLI quick start, running gaia init with Self-Host (Docker) scaffolds this structure automatically.

Quick Start

1

Install CLI and initialize self-host mode

Choose Self-Host (Docker) in the setup wizard.
2

Start the stack

3

Verify and monitor

Access the applications:

Service Overview

The Docker Compose setup includes the following services:
Next.js React Application
  • Port: 3000
  • Hot reload enabled in development
  • Serves the web interface
FastAPI Python Application
  • Port: 8000
  • Auto-reload enabled in development
  • RESTful API and WebSocket support
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
Worker - Handles scheduled tasks and background processing ARQ Worker - Async task queue processing RabbitMQ - Message broker (Port: 5672, Management: 15672)

Persistent Workspace Storage (JuiceFS and FUSE)

GAIA’s per-user workspace (file uploads, agent artifacts, installed skills) is backed by JuiceFS, 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: 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.
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.

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:
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.
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.
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.
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.
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.

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: 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 for the full list.

Docker Commands

Starting and Stopping

Viewing Logs

Service Management

Development Mode

For development, use the default docker-compose.yml:
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:

Data Persistence

Data is persisted using Docker volumes:
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:

Troubleshooting

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.
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).
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.

Alternative: CLI Setup

For a simplified setup experience with automatic configuration, see the CLI Setup Guide. The CLI handles all the Docker commands, environment setup, and health checks automatically.

Next Steps

Environment Variables

Configure your API keys and settings