Skip to main content

Deploying AFFiNE

This guide walks you through deploying AFFiNE using Docker Compose, the recommended method for self-hosting.

Prerequisites

Before you begin, ensure you have:
  • Docker Engine 24.0 or later
  • Docker Compose 2.0 or later
  • At least 4 GB RAM available
  • 20 GB+ free disk space
  • A domain name (optional, for production)
  • Basic knowledge of Docker and Linux

Docker Compose Deployment

Step 1: Prepare the Environment

Create a directory for your AFFiNE deployment:
mkdir -p ~/affine-selfhost
cd ~/affine-selfhost

Step 2: Download Configuration Files

Download the official Docker Compose configuration:
wget https://raw.githubusercontent.com/toeverything/affine/main/.docker/selfhost/compose.yml
wget https://raw.githubusercontent.com/toeverything/affine/main/.docker/selfhost/.env.example -O .env
wget https://raw.githubusercontent.com/toeverything/affine/main/.docker/selfhost/config.example.json -O config/config.json

Step 3: Understanding the Docker Compose Stack

The compose.yml file defines the following services:
The main AFFiNE application server.
affine:
  image: ghcr.io/toeverything/affine:${AFFINE_REVISION:-stable}
  container_name: affine_server
  ports:
    - '${PORT:-3010}:3010'
  depends_on:
    redis:
      condition: service_healthy
    postgres:
      condition: service_healthy
    affine_migration:
      condition: service_completed_successfully
  volumes:
    - ${UPLOAD_LOCATION}:/root/.affine/storage
    - ${CONFIG_LOCATION}:/root/.affine/config
  environment:
    - REDIS_SERVER_HOST=redis
    - DATABASE_URL=postgresql://${DB_USERNAME}:${DB_PASSWORD}@postgres:5432/${DB_DATABASE:-affine}
    - AFFINE_INDEXER_ENABLED=false
  restart: unless-stopped
A one-time job that runs database migrations before the main application starts.
affine_migration:
  image: ghcr.io/toeverything/affine:${AFFINE_REVISION:-stable}
  container_name: affine_migration_job
  command: ['sh', '-c', 'node ./scripts/self-host-predeploy.js']
  depends_on:
    postgres:
      condition: service_healthy
    redis:
      condition: service_healthy
PostgreSQL 16 with pgvector extension for vector embeddings.
postgres:
  image: pgvector/pgvector:pg16
  container_name: affine_postgres
  volumes:
    - ${DB_DATA_LOCATION}:/var/lib/postgresql/data
  environment:
    POSTGRES_USER: ${DB_USERNAME}
    POSTGRES_PASSWORD: ${DB_PASSWORD}
    POSTGRES_DB: ${DB_DATABASE:-affine}
    POSTGRES_INITDB_ARGS: '--data-checksums'
  healthcheck:
    test: ['CMD', 'pg_isready', '-U', "${DB_USERNAME}", '-d', "${DB_DATABASE:-affine}"]
    interval: 10s
    timeout: 5s
    retries: 5
  restart: unless-stopped
Redis for caching, sessions, and job queues.
redis:
  image: redis
  container_name: affine_redis
  healthcheck:
    test: ['CMD', 'redis-cli', '--raw', 'incr', 'ping']
    interval: 10s
    timeout: 5s
    retries: 5
  restart: unless-stopped

Step 4: Configure Environment Variables

Edit the .env file with your configuration:
.env
# Version to deploy (stable, beta, or canary)
AFFINE_REVISION=stable

# Port for the application server
PORT=3010

# Server configuration for external access
AFFINE_SERVER_HTTPS=true
AFFINE_SERVER_HOST=affine.yourdomain.com
# Or use the combined external URL
# AFFINE_SERVER_EXTERNAL_URL=https://affine.yourdomain.com

# Data persistence locations
DB_DATA_LOCATION=~/.affine/self-host/postgres/pgdata
UPLOAD_LOCATION=~/.affine/self-host/storage
CONFIG_LOCATION=~/.affine/self-host/config

# Database credentials
DB_USERNAME=affine
DB_PASSWORD=your_secure_password_here
DB_DATABASE=affine
Always set a strong DB_PASSWORD. Never leave it empty in production.

Step 5: Start the Services

Launch the entire stack:
docker compose up -d
Verify all services are running:
docker compose ps
You should see:
NAME                 IMAGE                                    STATUS
affine_postgres      pgvector/pgvector:pg16                   Up (healthy)
affine_redis         redis                                    Up (healthy)
affine_server        ghcr.io/toeverything/affine:stable       Up

Step 6: Check Logs

Monitor the application logs:
docker compose logs -f affine
Wait for the message indicating the server is ready:
🚀 AFFiNE Server is running on http://0.0.0.0:3010

Step 7: Access AFFiNE

Open your browser and navigate to:
  • Local: http://localhost:3010
  • Domain: https://affine.yourdomain.com (if configured)

Production Deployment with Reverse Proxy

For production deployments, use a reverse proxy like Nginx or Caddy to handle HTTPS.

Using Nginx

1

Install Nginx

sudo apt update
sudo apt install nginx certbot python3-certbot-nginx
2

Create Nginx Configuration

/etc/nginx/sites-available/affine
server {
    listen 80;
    server_name affine.yourdomain.com;
    
    location / {
        proxy_pass http://localhost:3010;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        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;
        
        # WebSocket support
        proxy_read_timeout 86400;
        proxy_send_timeout 86400;
    }
    
    client_max_body_size 100M;
}
3

Enable Site and Get SSL Certificate

sudo ln -s /etc/nginx/sites-available/affine /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
sudo certbot --nginx -d affine.yourdomain.com

Using Caddy

Caddy automatically handles HTTPS:
Caddyfile
affine.yourdomain.com {
    reverse_proxy localhost:3010
    encode gzip
}
Start Caddy:
caddy run --config Caddyfile

Volume Persistence

The Docker Compose setup uses three persistent volumes:
Path: ~/.affine/self-host/postgres/pgdataStores all PostgreSQL data including:
  • User accounts and workspaces
  • Document content and history
  • Permissions and settings
  • Vector embeddings for search
Never delete this directory without a backup. It contains all your data.
Path: ~/.affine/self-host/storageStores uploaded files:
  • User avatars
  • Document attachments
  • Images and videos
  • Binary blobs
Path: ~/.affine/self-host/configStores configuration files:
  • config.json - Application configuration
  • Private keys (generated automatically)

Updating Your Deployment

To update to a new version:
1

Backup Your Data

# Create backup directory
mkdir -p ~/affine-backups/$(date +%Y%m%d)

# Backup database
docker compose exec postgres pg_dump -U affine affine > ~/affine-backups/$(date +%Y%m%d)/database.sql

# Backup storage and config
cp -r ~/.affine/self-host/storage ~/affine-backups/$(date +%Y%m%d)/
cp -r ~/.affine/self-host/config ~/affine-backups/$(date +%Y%m%d)/
2

Pull New Image

docker compose pull
3

Restart Services

docker compose down
docker compose up -d
4

Verify

docker compose logs -f affine

Troubleshooting

Check logs for errors:
docker compose logs affine
Common issues:
  • Database not ready: Wait for postgres to become healthy
  • Port already in use: Change PORT in .env
  • Permission issues: Check file ownership of volume directories
Verify database is running and healthy:
docker compose exec postgres pg_isready -U affine
Check database credentials match in .env
Ensure your reverse proxy is configured to handle WebSocket connections. Check:
  • Upgrade and Connection headers are passed
  • Long timeouts are configured
  • No buffering on WebSocket routes
For more troubleshooting help, visit the AFFiNE Discord or GitHub Discussions.

Next Steps