How to Deploy AFFiNE - Open-Source Knowledge Management Platform

Updated on 04 August, 2026
Deploy AFFiNE on a Linux server using Docker Compose with PostgreSQL, Redis, Traefik, automatic HTTPS, and collaborative workspace features.
How to Deploy AFFiNE - Open-Source Knowledge Management Platform header image

AFFiNE is an open-source workspace platform used to write docs, draw on whiteboards, and build databases. It provides a web interface for managing notes, tasks, and team projects in a single place. AFFiNE is widely used for personal note-taking, working together with teams, and organizing project data.

This article explains how to deploy AFFiNE on a Linux server using Docker Compose with PostgreSQL as the database, Redis for caching and background jobs, and Traefik for reverse proxy.

Prerequisites

Before you begin, you need to:

Set Up the Directory Structure, Configuration, and Environment Variables

AFFiNE requires a configuration file to define environment variables, database connections, and application settings. The setup includes persistent storage for AFFiNE, PostgreSQL, Redis, and uploaded files to make sure data is saved when the server restarts. It also uses Traefik as a reverse proxy and to manage TLS certificates for secure HTTPS access.

  1. Create the project directory with subdirectories for configuration and data persistence.

    console
    $ mkdir -p ~/affine-server/{letsencrypt,redis}
    
    • affine-server: Holds environment and docker compose files, along with the data folders.
    • letsencrypt: Stores TLS certificates.
    • redis: Persists Redis data.
  2. Navigate to the project directory.

    console
    $ cd ~/affine-server
    
  3. Generate a 64-character secret key for the AFFiNE application.

    console
    $ openssl rand -hex 64
    

    Save the output for use in the environment file.

  4. Create the environment file.

    console
    $ nano .env
    
  5. Add the following configuration. Replace affine.example.com with your domain name, admin@example.com with your email address, STRONG_PASSWORD with a secure database password, and GENERATED_SECRET_KEY with the generated secret key from the previous step.

    ini
    DOMAIN=affine.example.com
    ACME_EMAIL=admin@example.com
    
    AFFINE_SERVER_HTTPS=true
    AFFINE_SERVER_HOST=affine.example.com
    AFFINE_SERVER_EXTERNAL_URL=https://affine.example.com
    AFFINE_SERVER_SECRET=GENERATED_SECRET_KEY
    
    AFFINE_REVISION=stable
    PORT=3010
    
    DB_DATA_LOCATION=./postgres
    UPLOAD_LOCATION=./storage
    CONFIG_LOCATION=./config
    
    DB_USERNAME=affine
    DB_PASSWORD=STRONG_PASSWORD
    DB_DATABASE=affine
    

    Save and close the file.

Deploy with Docker Compose

The deployment stack uses Traefik to handle reverse proxying and TLS certificate management and deploys AFFiNE, PostgreSQL, and Redis containers with mounted configuration, storage, and database volumes. This configuration is based on the official AFFiNE Docker Compose file.

  1. Create the Docker Compose file.

    console
    $ nano docker-compose.yaml
    
  2. Add the following configuration.

    yaml
    name: affine
    services:
      traefik:
        image: traefik:v3.7
        container_name: traefik
        command:
          - "--providers.docker=true"
          - "--providers.docker.exposedbydefault=false"
          - "--entrypoints.web.address=:80"
          - "--entrypoints.websecure.address=:443"
          - "--entrypoints.web.http.redirections.entryPoint.to=websecure"
          - "--entrypoints.web.http.redirections.entryPoint.scheme=https"
          - "--certificatesresolvers.myresolver.acme.tlschallenge=true"
          - "--certificatesresolvers.myresolver.acme.email=${ACME_EMAIL}"
          - "--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json"
        ports:
          - "80:80"
          - "443:443"
        volumes:
          - "/var/run/docker.sock:/var/run/docker.sock:ro"
          - "./letsencrypt:/letsencrypt"
        restart: unless-stopped
    
      affine:
        image: ghcr.io/toeverything/affine:${AFFINE_REVISION:-stable}
        container_name: affine_server
        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
        env_file:
          - .env
        environment:
          - REDIS_SERVER_HOST=redis
          - DATABASE_URL=postgresql://${DB_USERNAME}:${DB_PASSWORD}@postgres:5432/${DB_DATABASE:-affine}
          - AFFINE_INDEXER_ENABLED=false
        labels:
          - "traefik.enable=true"
          - "traefik.http.routers.affine.rule=Host(`${DOMAIN}`)"
          - "traefik.http.routers.affine.entrypoints=websecure"
          - "traefik.http.routers.affine.tls.certresolver=myresolver"
          - "traefik.http.services.affine.loadbalancer.server.port=3010"
        restart: unless-stopped
    
      affine_migration:
        image: ghcr.io/toeverything/affine:${AFFINE_REVISION:-stable}
        container_name: affine_migration_job
        volumes:
          - ${UPLOAD_LOCATION}:/root/.affine/storage
          - ${CONFIG_LOCATION}:/root/.affine/config
        command: ['sh', '-c', 'node ./scripts/self-host-predeploy.js']
        env_file:
          - .env
        environment:
          - REDIS_SERVER_HOST=redis
          - DATABASE_URL=postgresql://${DB_USERNAME}:${DB_PASSWORD}@postgres:5432/${DB_DATABASE:-affine}
          - AFFINE_INDEXER_ENABLED=false
        depends_on:
          postgres:
            condition: service_healthy
          redis:
            condition: service_healthy
    
      redis:
        image: redis
        container_name: affine_redis
        volumes:
          - ./redis:/data
        healthcheck:
          test: ['CMD', 'redis-cli', '--raw', 'incr', 'ping']
          interval: 10s
          timeout: 5s
          retries: 5
        restart: unless-stopped
    
      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
    

    Save and close the file.

    In the above manifest:

    • services: Four containers work together to deliver the workspace platform with HTTPS enabled.
      • traefik: Serves as the reverse proxy and TLS termination point with automatic Let's Encrypt certificate provisioning.
      • postgres: Stores the core database, including docs, whiteboards, and user profiles.
      • redis: Acts as a fast memory store for real-time team collaboration.
      • affine: Runs the AFFiNE web server and provides the user interface.
    • image: Uses official container images published by the upstream projects.
    • environment (postgres): Defines the database name, username, and password using the values from the .env file.
    • environment (affine): Configures the public URLs, connects to Redis, and formats the connection string to link the app to the PostgreSQL database.
    • volumes:
      • Mapped folders: Local folders from your .env file (like ./postgres and ./storage) that save database files and user uploads across container restarts.
      • ./redis: A mapped local folder that stores Redis data.
      • ./letsencrypt: A mapped local folder that stores the TLS certificates.
    • ports: Ports 80 and 443 open HTTP and HTTPS traffic through Traefik.
    • labels (affine): These tell Traefik how to route HTTPS traffic to AFFiNE and how to set up the free TLS certificate for your specific domain.
    • depends_on: Ensures the affine service waits for postgres to be fully healthy and redis to start before running.
    • restart: unless-stopped: Ensures containers automatically restart if they fail or if the server reboots.
  3. Start all services in detached mode.

    console
    $ docker compose up -d
    
  4. Verify that the services are running.

    console
    $ docker compose ps
    

    The output displays all the containers in running state, with traefik listing ports 80 and 443 under PORTS.

  5. Check the service logs to confirm AFFiNE loaded the configuration successfully.

    console
    $ docker compose logs --tail=50
    

    For more information on managing a Docker Compose stack, see the How To Use Docker Compose article.

Access and Configure AFFiNE

AFFiNE provides a web interface to manage your docs, whiteboards, and projects. The first time you access the app, you need to create an administrator account and set up your initial workspace.

  1. Open your web browser and navigate to the AFFiNE at https://affine.example.com, replacing affine.example.com with your configured domain.

    AFFiNE initial login screen

    The page displays the welcome message, confirming HTTPS routing through Traefik is working correctly.

  2. On the screen, enter your Name, Email Address and a secure Password to create the initial administrator account.

  3. Click Continue to access the admin dashboard.

    AFFiNE dashboard after login

Demonstrate Application Use Case by Performing Actions

A sample use case verifies that PostgreSQL is persisting data and that the application's core features are fully functional. The following steps demonstrate AFFiNE's core capabilities by creating a workspace, documenting information, and organizing content using the visual Edgeless canvas.

Create a New Workspace

A workspace groups related docs and whiteboards under a shared team or project.

  1. After creating the administrator account, navigate again to https://affine.example.com, replacing affine.example.com with your configured domain.

  2. Open the default Demo Workspace. Click Create Workspace.

  3. Enter a workspace name such as Team Knowledge Base. Under Workspace type, select AFFiNE SelfHosted Cloud.

  4. Click Create to create the workspace.

    AFFiNE workspace creation

Write and Format a Note

Docs use a rich text editor for writing and structuring content within a workspace.

  1. Open the Team Knowledge Base workspace.

  2. Click the + icon in the sidebar to create a new page.

  3. Enter a title such as Project Planning Notes.

  4. Create a heading named Project Overview and add a brief description of the workspace purpose.

  5. Create another heading named Key Objectives.

  6. Add a numbered list to organize project goals and tasks.

    AFFiNE document editor

Switch to the Whiteboard or Edgeless View

Edgeless view turns a doc into a visual canvas for diagramming and freeform layout.

  1. Open the Project Planning Notes document in the Team Knowledge Base workspace.

  2. Click the Edgeless View icon in the upper-left corner.

  3. Use the canvas toolbar to access visual collaboration tools such as shapes, connectors, drawing tools, and text annotations.

    AFFiNE edgeless view

Create Linked Content or Add Visual Blocks

Connectors on the canvas link related blocks together, making relationships between pieces of content visible.

  1. Open the Project Planning Notes document in Edgeless View.

  2. Use the canvas toolbar to add a new text block.

  3. Enter a title such as Project Resources to represent project information.

  4. Use the connector tool to create a visual link between the Project Planning Notes document and the Project Resources block.

    AFFiNE linked visual blocks

Conclusion

You have successfully deployed AFFiNE on your server using Docker Compose. This creates a workspace for writing and formatting notes, switching to the Edgeless canvas, and linking visual content. It provides a unified workspace for documents, knowledge management, and visual collaboration, making it suitable for both individuals and teams. To learn more about advanced configuration options and features, refer to the official AFFiNE documentation.

Comments