Affine

Affine is an open-source knowledge management platform, similar to Notion. You can organise and collaborate on documents and wikis, with support for markdown, rich embeds, and full-text search.

This compose file deploys Affine with a pgvector database, Valkey for caching, and Manticore Search for full-text indexing. A migration job runs automatically before the main service starts. Volumes are stored on NFS, and the Affine UI is proxied through a Traefik reverse proxy with Cloudflare TLS certificates.

Docker Compose

# compose.yaml

services:
  affine:
    image: ghcr.io/toeverything/affine:stable
    container_name: affine-server
    restart: unless-stopped
    depends_on:
      affine_valkey:
        condition: service_healthy
      affine_postgres:
        condition: service_healthy
      affine_migration:
        condition: service_completed_successfully
      affine_manticore:
        condition: service_healthy
    # ports:
    #  - "3010:3010"
    networks:
      - affine
      - affine_proxy
    volumes:
      - type: volume
        source: docker-nfs
        target: /root/.affine/storage
        volume:
          subpath: affine/upload
      - type: volume
        source: docker-nfs
        target: /root/.affine/config
        volume:
          subpath: affine/config
    environment:
      REDIS_SERVER_HOST: affine_valkey
      REDIS_SERVER_PASSWORD: ${VALKEY_PASSWORD}
      DATABASE_URL: postgresql://${DB_USERNAME}:${DB_PASSWORD}@affine_postgres:5432/${DB_DATABASE}
      AFFINE_INDEXER_ENABLED: true
      AFFINE_INDEXER_SEARCH_ENDPOINT: http://affine_manticore:9308
      TZ: Europe/London
    labels:
      - "traefik.enable=true"
      - "traefik.docker.network=affine_proxy"

      - "traefik.http.services.affine.loadbalancer.server.port=3010"

      - "traefik.http.routers.affine.rule=Host(`affine.${TRAEFIK_BASE_URL}`)"
      - "traefik.http.routers.affine.entrypoints=websecure"
      - "traefik.http.routers.affine.tls.certresolver=cloudflare"

  affine_migration:
    image: ghcr.io/toeverything/affine:stable
    container_name: affine-migration-job
    restart: no
    depends_on:
      affine_postgres:
        condition: service_healthy
      affine_valkey:
        condition: service_healthy
    networks:
      - affine
    volumes:
      - type: volume
        source: docker-nfs
        target: /root/.affine/storage
        volume:
          subpath: affine/upload
      - type: volume
        source: docker-nfs
        target: /root/.affine/config
        volume:
          subpath: affine/config
    command: ["sh", "-c", "node ./scripts/self-host-predeploy.js"]
    environment:
      REDIS_SERVER_HOST: affine_valkey
      REDIS_SERVER_PASSWORD: ${VALKEY_PASSWORD}
      DATABASE_URL: postgresql://${DB_USERNAME}:${DB_PASSWORD}@affine_postgres:5432/${DB_DATABASE}
      AFFINE_INDEXER_ENABLED: true
      AFFINE_INDEXER_SEARCH_ENDPOINT: http://affine_manticore:9308
      TZ: Europe/London

  affine_manticore:
    image: manticoresearch/manticore:14.1.0
    container_name: affine_manticore
    restart: unless-stopped
    networks:
      - affine
    volumes:
      - type: volume
        source: docker-nfs
        target: /var/lib/manticore
        volume:
          subpath: affine/manticore
    ulimits:
      nproc: 65535
      nofile:
        soft: 65535
        hard: 65535
      memlock:
        soft: -1
        hard: -1
    healthcheck:
      test: ["CMD", "wget", "-O-", "http://127.0.0.1:9308"]
      interval: 10s
      timeout: 5s
      retries: 5
    environment:
      TZ: Europe/London

  affine_valkey:
    image: valkey/valkey:9
    container_name: affine_valkey
    restart: unless-stopped
    networks:
      - affine
    volumes:
      - type: volume
        source: docker-nfs
        target: /data
        volume:
          subpath: affine/valkey
    healthcheck:
      test: ["CMD", "valkey-cli", "--raw", "incr", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5
    command: valkey-server --save 30 1 --loglevel warning --requirepass ${VALKEY_PASSWORD}
    environment:
      TZ: Europe/London

  affine_postgres:
    image: pgvector/pgvector:pg18-trixie
    container_name: affine_postgres
    restart: unless-stopped
    networks:
      - affine
    volumes:
      - type: volume
        source: docker-nfs
        target: /var/lib/postgresql
        volume:
          subpath: affine/postgres
    healthcheck:
      test:
        ["CMD", "pg_isready", "-U", "${DB_USERNAME}", "-d", "${DB_DATABASE}"]
      interval: 10s
      timeout: 5s
      retries: 5
    environment:
      POSTGRES_USER: ${DB_USERNAME}
      POSTGRES_PASSWORD: ${DB_PASSWORD}
      POSTGRES_DB: ${DB_DATABASE}
      POSTGRES_INITDB_ARGS: "--data-checksums"
      TZ: Europe/London

volumes:
  docker-nfs:
    driver: local
    driver_opts:
      type: nfs
      o: addr=xxx.xxx.xxx.xxx,nolock,soft,rw,nfsvers=4.2
      device: :/mnt/nfs-volume

networks:
  affine:
    name: affine
  affine_proxy:
    name: affine_proxy

Environment Variables

# .env

AFFINE_REVISION=stable

TRAEFIK_BASE_URL=example.com
AFFINE_SERVER_EXTERNAL_URL=https://affine.${TRAEFIK_BASE_URL}

DB_USERNAME=
DB_PASSWORD=
DB_DATABASE=

VALKEY_PASSWORD=

Traefik Configuration

# compose.yaml (excerpt)

services:
  traefik:
    image: traefik:latest
    container_name: traefik
    ...
    networks:
      - traefik
      # here
      - affine_proxy
    ...

networks:
  # here
  affine_proxy:
    name: affine_proxy