Files
homelab-opentofu/modules/20-services-apps/affine
Yuris Cakranegara b73c7ab21d feat: add AFFiNE
2025-06-18 22:23:57 +10:00
..
2025-06-18 22:23:57 +10:00
2025-06-18 22:23:57 +10:00
2025-06-18 22:23:57 +10:00

AFFiNE Module

This module deploys AFFiNE, a privacy-first, local-first, note-taking and knowledge base application, as Docker containers in the homelab environment.

Overview

The AFFiNE module:

  • Deploys four Docker containers:
    • affine_server: The main AFFiNE application server
    • affine_migration_job: A container that runs pre-deployment migrations
    • affine_postgres: A PostgreSQL (pgvector) database backend
    • affine_redis: A Redis instance for caching and temporary data
  • Creates a dedicated Docker network (affine-network) for container communication
  • Persists data to volumes on the host
  • Provides service definition for integration with networking modules

Usage

module "affine" {
  source      = "./modules/20-services-apps/affine"
  volume_path = "/path/to/volumes/affine"
  networks    = ["homelab-network"]
}

Variables

Variable Description Type Default
image_tag Tag of the AFFiNE image to use string "stable"
volume_path Host path for AFFiNE and database data volumes string -
networks List of additional networks to which AFFiNE should be attached list(string) []

Outputs

Output Description
service_definition Service definition for integration with networking modules

Service Definition

This module outputs a service definition that is used by the networking modules to expose the service.

{
  name         = "affine_server"
  primary_port = 3010
  endpoint     = "http://affine_server:3010"
  subdomains   = ["affine"]
  publish_via  = "tunnel"  # Only publish through Cloudflare tunnel
}

Environment Variables

AFFiNE requires several environment variables to function properly. These are stored in a .env file in the module directory and read using the dotenv Terraform provider:

  • Database configuration:

    • DB_USERNAME: PostgreSQL user
    • DB_PASSWORD: PostgreSQL password
    • DB_DATABASE: Database name (defaults to "affine")
  • AFFiNE configuration:

    • AFFINE_REVISION: Version of AFFiNE to use ("stable" or "canary") (defaults to "canary")
    • PORT: External port for the AFFiNE server (defaults to 3010)
    • AFFINE_SERVER_HTTPS: Whether to use HTTPS (defaults to "true")
    • AFFINE_SERVER_HOST: Hostname for the AFFiNE server
    • AFFINE_SERVER_NAME: Name for the AFFiNE server (defaults to "AFFiNE Selfhosted")
  • Cloudflare R2 configuration:

    • R2_OBJECT_STORAGE_ACCOUNT_ID: Cloudflare R2 account ID
    • R2_OBJECT_STORAGE_ACCESS_KEY_ID: Cloudflare R2 access key ID
    • R2_OBJECT_STORAGE_SECRET_ACCESS_KEY: Cloudflare R2 secret access key

Data Persistence

AFFiNE stores its data in three main volumes:

  1. AFFiNE application data: /root/.affine/storage in the container, mapped to ${volume_path}/self-host/storage on the host
  2. AFFiNE configuration: /root/.affine/config in the container, mapped to ${volume_path}/self-host/config on the host
  3. PostgreSQL data: /var/lib/postgresql/data in the container, mapped to ${volume_path}/self-host/postgres/pgdata on the host

Networking

The module creates a dedicated Docker network named affine-network for communication between the AFFiNE components. The AFFiNE server container is also attached to any additional networks specified in the networks variable, allowing it to communicate with other services in the homelab.

Dependencies

The AFFiNE containers have the following dependencies:

  • The main affine_server depends on PostgreSQL, Redis, and the migration job
  • The migration job depends on PostgreSQL and Redis
  • Both PostgreSQL and Redis use healthchecks to ensure they're ready before dependent services start

Integration with Networking Modules

This service is configured to be exposed through the Caddy reverse proxy, set by publish_via = "reverse_proxy".

Example Integration in Main Configuration

module "affine" {
  source      = "./modules/20-services-apps/affine"
  volume_path = module.system_globals.volume_host
  networks    = [module.services.homelab_docker_network_name]
}

# The service definition is automatically included in the services output
module "services" {
  source = "./modules/services"
  # ...
  service_definitions = [
    module.affine.service_definition,
    # Other service definitions
  ]
}