Try describing what you need:

macro logo

macro

★ 6.2k

A self-hosted alternative to Slack, Notion, Asana

About

Macro is presented as a unified workspace for small teams and startups that want email, chat, documents, tasks, CRM, and agents to behave like one connected system. The README frames it as an...

Community Ratings

No ratings yet
Frame 11

Features

  • Multi-account unified inbox
  • Shared inboxes
  • Unified inbox across email, messages, mentions, and tasks
  • Keyboard shortcuts
  • AI-assisted drafting and search
  • Built-in window manager with multiple splits
  • Native CRM objects
  • Real-time collaborative markdown docs
  • CRDT-based collaboration
  • Offline editing and reconciliation

Details

Last Updated
Nov 9, 2026
Created
Nov 8, 2025
Requirements
Cloudflare durable objects
API
REST
Backup & Export
file-backupnative-export
Privacy & Independence
Cloud: OptionalOffline Capable
User Model
Multi-User
Deployment

Deployment: Docker Compose ✓22 Containers — heavy hosting footprintRuns as 22 containers

Run macro yourself

Pick how much you want to manage.

1 more way to host

Some links earn us a commission. netcup pays us one and is our preferred VPS host.

Is this your project? Sponsor a listing

Run macro with Docker Compose

Compose file from the macro project repository.

22 services · infra: nginx · 2 unclassified (all-in-one, agent)

no resource limits declared

needs persistent storage (5 volumes)

docker/docker-compose.yml · @ dd1eee5 · scanned Nov 3, 2026

# Services
# Freeze the Docker Compose project name so every checkout/worktree uses the
# same local containers, networks, and anonymous Compose-managed resources.
# Local setup is single-instance by design.
name: macro

include:
  - docker/docker-compose-databases.yml
  - infra/stacks/fusionauth-instance/docker-compose.yml

x-common-env: &common-env
  # The xtask orchestrator points this at a per-instance generated env file via
  # MACRO_ENV_FILE (run-local/run-dev). Defaults to .env for any legacy/manual
  # `docker compose` invocation, and is optional so a missing file is not fatal.
  env_file:
    - path: ${MACRO_ENV_FILE:-.env}
      required: false

x-rust-services-image: &rust-services-image
  image: macro-local-rust-services:dev
  build:
    context: .
    dockerfile: docker/Dockerfile.dev
    target: services_bundle

services:
  rust_services_image:
    profiles:
      - build-helper
    build:
      context: .
      dockerfile: docker/Dockerfile.dev
      target: services_bundle
    image: macro-local-rust-services:dev

  # ============================================================================
  # authentication-service (Port 8080)
  # Primary auth service - depends on FusionAuth, Redis, MacroDB
  # ============================================================================
  authentication-service:
    <<: [*common-env, *rust-services-image]
    command: ["/app/out/authentication_service"]
    ports:
      - "8080:8080"
    depends_on:
      - postgres
      - fusionauth
      - redis
    networks:
      - auth
      - databases
      - services
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 10s

  # ============================================================================
  # connection_gateway (Port 8082)
  # WebSocket gateway - Redis only
  # ============================================================================
  connection_gateway:
    <<: [*common-env, *rust-services-image]
    command: ["/app/out/connection_gateway_service"]
    ports:
      - "8082:8080"
    depends_on:
      - redis
    networks:
      databases:
      services:
        aliases:
          - connection-gateway
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 10s

  # ============================================================================
  # contacts_service (Port 8083)
  # Contact management - ContactsDB
  # ============================================================================
  contacts_service:
    <<: [*common-env, *rust-services-image]
    command: ["/app/out/contacts_service"]
    ports:
      - "8083:8080"
    networks:
      databases:
      services:
        aliases:
          - contacts-service
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 10s

  # ============================================================================
  # document_cognition_service (Port 8085)
  # Document analysis and processing
  # ============================================================================
  document_cognition_service:
    <<: [*common-env, *rust-services-image]
    command: ["/app/out/document_cognition_service"]
    ports:
      - "8085:8080"
    depends_on:
      - document_storage_service
      - email_service
      - static_file_service
      - sync_service
      - lexical_service
      - ai_editing_worker
    environment:
      - OVERRIDE_DOCUMENT_STORAGE_SERVICE_URL=http://document-storage-service:8080
      - OVERRIDE_AI_EDITING_WORKER_URL=http://ai-editing-worker:8933
      # The import pipeline (and chat's CreateDocument) create documents from
      # inside DCS, which initializes content through lexical + sync.
      - OVERRIDE_SYNC_SERVICE_URL=http://sync-service:8787
      - OVERRIDE_LEXICAL_SERVICE_URL=http://lexical-service:8096
      # Import/ai-projection completions push gateway nudges from inside DCS.
      - OVERRIDE_CONNECTION_GATEWAY_URL=http://connection-gateway:8080
    networks:
      databases:
      services:
        aliases:
          - document-cognition-service
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 10s

  # ============================================================================
  # document_storage_service (Port 8086)
  # Main document storage API
  # ============================================================================
  document_storage_service:
    <<: [*common-env, *rust-services-image]
    command: ["/app/out/document_storage_service"]
    ports:
      - "8086:8080"
    environment:
      # - OVERRIDE_SYNC_SERVICE_URL=https://sync-service-playground.macroverse.workers.dev
      # uncomment above, and comment out below, for when you want to use the playground sync service. also use
      # VITE_SYNC_SERVICE_HOST=sync-service-playground.macroverse.workers.dev VITE_LOCAL_SERVERS=ALL bun run dev
      # if you do so
      - OVERRIDE_SYNC_SERVICE_URL=http://sync-service:8787
      - OVERRIDE_LEXICAL_SERVICE_URL=http://lexical-service:8096
    depends_on:
      - redis
      - connection_gateway
      - authentication-service
      - sync_service
      - lexical_service
    networks:
      databases:
      services:
        aliases:
          - document-storage-service
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 10s

  # ============================================================================
  # document_upload_finalizer (local worker)
  # LocalStack S3 ObjectCreated -> SQS consumer for document upload finalization.
  # Production uses the document_upload_finalizer_handler Lambda.
  # ============================================================================
  document_upload_finalizer:
    <<: [*common-env, *rust-services-image]
    command: ["/app/out/document_upload_finalizer_local_worker"]
    environment:
      LOCAL_AWS_URL: ${LOCAL_AWS_URL:-http://localstack:4566}
      DOCUMENT_UPLOAD_FINALIZER_QUEUE_URL: ${DOCUMENT_UPLOAD_FINALIZER_QUEUE_URL:-http://localstack:4566/000000000000/document-upload-finalizer-queue}
    depends_on:
      - postgres
      - sync_service
      - lexical_service
    networks:
      - databases
      - services

  # ============================================================================
  # email_service (Port 8087)
  # Email processing and management
  # ============================================================================
  email_service:
    <<: [*common-env, *rust-services-image]
    command: ["/app/out/email_service"]
    ports:
      - "8087:8080"
    depends_on:
      - authentication-service
      - document_storage_service
      - connection_gateway
      - static_file_service
      - redis
    networks:
      databases:
      services:
        aliases:
          - email-service
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 10s

  # ============================================================================
  # email_pubsub_workers (local worker)
  # Consumes the email SQS queues (backfill, scheduled, gmail sync/ops, sfs,
  # link manager). Runs as its own deployment in prod; locally it's its own
  # container alongside email_service. No HTTP port — pure SQS consumer.
  # ============================================================================
  email_pubsub_workers:
    <<: [*common-env, *rust-services-image]
    command: ["/app/out/pubsub_workers"]
    depends_on:
      - postgres
      - redis
      - authentication-service
      - document_storage_service
      - connection_gateway
      - static_file_service
      - email_service
    networks:
      - databases
      - services

  # ============================================================================
  # notification_service (Port 8089)
  # User notifications - NotificationDB
  # ============================================================================
  notification_service:
    <<: [*common-env, *rust-services-image]
    command: ["/app/out/notification_service"]
    ports:
      - "8089:8080"
    environment:
      LAST_ONLINE_REDIS_URI: ${LAST_ONLINE_REDIS_URI:-${REDIS_URI:-redis://redis:6379}}
      OVERRIDE_CONNECTION_GATEWAY_URL: http://connection-gateway:8080
    depends_on:
      - redis
      - document_cognition_service
      - authentication-service
      - connection_gateway
      - document_storage_service
    networks:
      databases:
      services:
        aliases:
          - notification-service
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 10s

  # ============================================================================
  # search_processing_service (Port 8092)
  # Search indexing worker
  # ============================================================================
  search_processing_service:
    <<: [*common-env, *rust-services-image]
    command: ["/app/out/search_processing_service"]
    ports:
      - "8092:8080"
    environment:
      - OVERRIDE_LEXICAL_SERVICE_URL=http://lexical-service:8096
    depends_on:
      - email_service
      - lexical_service
    networks:
      databases:
      services:
        aliases:
          - search-processing-service
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 10s

  # ============================================================================
  # static_file_service (Port 8094)
  # Static file serving - DynamoDB
  # ============================================================================
  static_file_service:
    <<: [*common-env, *rust-services-image]
    command: ["/app/out/static_file_service"]
    ports:
      - "8094:8080"
    networks:
      databases:
      services:
        aliases:
          - static-file-service
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/api/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 10s

  # ============================================================================
  # static_file_cdn (Port 8100)
  # Local CloudFront emulation - routes to S3 and static_file_service
  # ============================================================================
  static_file_cdn:
    image: nginx:alpine
    ports:
      - "8100:80"
    volumes:
      - ./infra/local/nginx/static-file-cdn.conf:/etc/nginx/conf.d/default.conf:ro
    depends_on:
      - static_file_service
    networks:
      databases:
      services:
        aliases:
          - static-file-cdn

  # ============================================================================
  # unfurl_service (Port 8095)
  # URL unfurling - no dependencies
  # ============================================================================
  unfurl_service:
    <<: [*common-env, *rust-services-image]
    command: ["/app/out/unfurl_service"]
    ports:
      - "8095:8080"
    networks:
      services:
        aliases:
          - unfurl-service
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 10s

  # ============================================================================
  # image_proxy_service (Port 8097)
  # Proxies external images to avoid CORS issues and IP leakage
  # ============================================================================
  image_proxy_service:
    <<: [*common-env, *rust-services-image]
    command: ["/app/out/image_proxy_service"]
    ports:
      - "8097:8080"
    networks:
      services:
        aliases:
          - image-proxy-service
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 10s

  # ============================================================================
  # websocket_service (Port 6969)
  # Simple WebSocket service - responds with "ping" to all messages
  # ============================================================================
  websocket_service:
    build:
      context: .
      dockerfile: docker/websocket-service.Dockerfile
    ports:
      - "6969:6969"
    networks:
      services:
        aliases:
          - websocket-service

  # ============================================================================
  # sync_service (Port 8787)
  # Document sync service using Cloudflare Workers/Durable Objects (via miniflare)
  # ============================================================================
  sync_service:
    <<: *common-env
    build:
      context: .
      dockerfile: docker/sync-service.Dockerfile
    ports:
      - "8787:8787"
    volumes:
      - ./services/sync-service/src:/app/src
    environment:
      - INTERNAL_API_SECRET_KEY=${INTERNAL_API_SECRET_KEY}
      - DOCUMENT_PERMISSIONS_SECRET=${DOCUMENT_PERMISSION_JWT}
      - DSS_INTERNAL_AUTH_KEY=${DOCUMENT_STORAGE_SERVICE_AUTH_KEY}
      # This worker exports OTLP/JSON over HTTP, so it needs the
      # HTTP intake (:4318) — NOT the gRPC :4317 endpoint the
      # shared env (env_layer) injects for the .with_tonic() Rust services.
      # Pinning it here overrides the shared env_file value for this service.
      - OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4318
    networks:
      services:
        aliases:
          - sync-service
      databases:
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8787/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 60s

  # ============================================================================
  # ai_editing_worker (Port 8933)
  # AI-powered document editing Cloudflare Worker (local wrangler dev)
  # ============================================================================
  ai_editing_worker:
    <<: *common-env
    build:
      context: .
      dockerfile: docker/ai-editing-worker.Dockerfile
    ports:
      - "8933:8933"
    volumes:
      - .:/app
      # .wrangler (incl. the local D1 trace shard) persists to the host via the
      # repository mount above, so `bun run traces --env local --local` works from the
      # host without docker exec. An anonymous volume used to shadow this path.
    depends_on:
      - sync_service
    networks:
      services:
        aliases:
          - ai-editing-worker

  # ============================================================================
  # analytics_proxy (Port 8098)
  # First-party analytics/telemetry proxy (Cloudflare Worker, local wrangler
  # dev). Fronts PostHog and OTLP telemetry so ad blockers don't drop them and
  # the browser never holds the Datadog API key. Locally the
  # /i/otlp route forwards to the otel-collector alias (jaeger/datadog profile);
  # if neither profile is up, trace exports just fail harmlessly.
  # ============================================================================
  analytics_proxy:
    <<: *common-env
    build:
      context: .
      dockerfile: docker/analytics-proxy.Dockerfile
    ports:
      - "8098:8098"
    volumes:
      - .:/app
    networks:
      services:
        aliases:
          - analytics-proxy

  # ============================================================================
  # lexical_service (Port 8096)
  # Lexical document conversion service - converts Lexical JSON to various formats
  # ============================================================================
  lexical_service:
    <<: *common-env
    build:
      context: .
      dockerfile: docker/lexical-service.Dockerfile
      args:
        GITHUB_PACKAGES_TOKEN: ${GITHUB_PACKAGES_TOKEN}
    ports:
      - "8096:8096"
    depends_on:
      - sync_service
    # Bundle the entry before running. Executing the unbundled TS directly under
    # Bun evaluates the circular @lexical/* ESM imports in an order that triggers
    # a TDZ ("Cannot access 'HeadingNode'/'ElementNode' before initialization");
    # bundling hoists/orders the declarations and resolves it (matching how the
    # worker is run normally — wrangler/esbuild bundles it first). loro-crdt
    # stays external: bundling it emits its .wasm as a second output file
    # (breaking --outfile) and mis-wires the wasm instantiation at runtime.
    # The bundle must live under /app: bun resolves the external loro-crdt
    # import relative to the bundle's path, so it needs /app/node_modules.
    command: ["sh", "-c", "bun build src/server.ts --target=bun --external loro-crdt --outfile=/app/server.bundle.js && exec bun /app/server.bundle.js"]
    environment:
      - PORT=8096
      - INTERNAL_AUTH_KEY=${INTERNAL_API_SECRET_KEY}
      - SYNC_SERVICE_AUTH_KEY=${INTERNAL_API_SECRET_KEY}
      - SYNC_SERVICE_URL=http://sync-service:8787
    networks:
      services:
        aliases:
          - lexical-service
    healthcheck:
      test: ["CMD", "bun", "-e", "fetch('http://localhost:8096/health').then(r => process.exit(r.ok ? 0 : 1)).catch(() => process.exit(1))"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 10s

  # ============================================================================
  # jaeger (UI port 16686, OTLP 4317/4318) — profile: jaeger
  # Fully local trace viewer, no account needed. UI at http://localhost:16686.
  # Start: docker compose --project-directory . -f docker/docker-compose.yml up -d jaeger
  # ============================================================================
  jaeger:
    profiles:
      - jaeger
    image: jaegertracing/all-in-one:latest
    environment:
      COLLECTOR_OTLP_ENABLED: "true"
    # Browser -> collector requests need CORS on the OTLP HTTP receiver.
    # allowed-headers must include Content-Type: jaeger's default rejects the
    # preflight the browser OTLP exporter sends, even with origins=*.
    command:
      [
        "--collector.otlp.http.cors.allowed-origins=*",
        "--collector.otlp.http.cors.allowed-headers=*",
      ]
    ports:
      - "16686:16686"
      - "4317:4317"
      - "4318:4318"
    networks:
      services:
        aliases:
          - otel-collector

  # ============================================================================
  # datadog-agent (OTLP 4317/4318) — profile: datadog
  # Forwards OTLP traces to Datadog (us5); no local UI. View at
  # us5.datadoghq.com -> APM -> Traces, filtered by env:local (DD_ENV).
  # Binds the same OTLP ports as jaeger — run one or the other, not both.
  # Start: DD_API_KEY=<key> docker compose --project-directory . -f docker/docker-compose.yml up -d datadog-agent
  # ============================================================================
  datadog-agent:
    profiles:
      - datadog
    image: gcr.io/datadoghq/agent:7
    environment:
      # Default-empty (not :?) so the var is only required when this profile
      # actually runs; the agent itself fails fast without a key.
      DD_API_KEY: ${DD_API_KEY:-}
      DD_SITE: us5.datadoghq.com
      DD_ENV: ${DD_ENV:-local}
      DD_HOSTNAME: local-dev
      DD_APM_ENABLED: "true"
    volumes:
      # Mounted config exists to enable CORS on the OTLP HTTP receiver
      # (browser -> agent); that setting has no env-var form.
      - ./docker/datadog-agent/datadog.yaml:/etc/datadog-agent/datadog.yaml:ro
    ports:
      - "4317:4317"
      - "4318:4318"
    networks:
      services:
        aliases:
          - otel-collector

networks:
  services:
    driver: bridge
  databases:
    external: true
  auth:
    external: true

This file may be out of date. Check the macro documentation for current setup steps.

Track your self-hosted stack

Bookmark software to try, rate tools you've used, and keep your collection in one place.

Metadata extracted from README on Aug 10, 2026

Alternatives to macro

rowboat logo

rowboat

18.1k
Rowboat is an open-source personal AI assistant that indexes your work into a local knowledge graph and provides built-in tools like email, notes, browser, code mode, and meeting notes.
AI AgentsKnowledge ManagementAI Coding Assistant+2
Fully FOSS
Details
odysseus logo

odysseus

95.5k
Odysseus is a self-hosted AI workspace for chat, agents, research, documents, email, notes, calendar, and local model workflows.
Artificial IntelligenceAI AgentsAI Interfaces+3
4 Containers
Details
buzz logo

buzz

36.8k
Buzz is a self-hostable workspace where humans and AI agents collaborate in shared rooms backed by a signed event log.
Self Hosting SolutionsCustom CommunicationsKnowledge Management+2
7 Containers
Details
qm logo

qm

15.7k
QM is a multiplayer agent harness for work, designed for use in Slack and on the web with isolated per-person and per-room workspaces.
Self Hosting SolutionsAutomation ToolsAI Agents+2
Details

Related Software

Relaticle logo

Relaticle

1.8k
Relaticle is a self-hosted open-source CRM with an MCP server for AI agents and a REST API for CRM operations and analysis.
Self Hosting SolutionsAnalytics ToolsAI Agents+3
5 ContainersFully FOSS
Details
crm logo

crm

11.9k
Comp AI CRM is an open-source CRM designed for AI agents and built around a durable autonomous agent that researches, records facts, and manages follow-ups.
CRM SystemsAI AgentsAutomation Tools+3
Single Container
Details
Twenty logo

Twenty

60.7k
An open-source CRM for managing customer data, permissions, workflows, emails, and related records.
CRM SystemsSelf Hosting SolutionsAutomation Tools+4
FOSS with Issues
Details
Django-CRM logo

Django-CRM

648
A free, self-hosted CRM built with Python and Django for managing leads, deals, contacts, tasks, email campaigns, and analytics.
CRM SystemsTask ManagementAnalytics Tools+4
Details
Revert logo

Revert

1.0k
An open-source unified API platform for building and self-hosting product integrations with third-party services.
API ManagementAutomation ToolsAuthentication Services+2
Details
Chatwoot logo

Chatwoot

39.3k
An open-source, self-hosted customer support platform for managing conversations across channels.
Custom CommunicationsSelf Hosting SolutionsTicketing Systems+2
4 Containers
Details
MindsDB logo

MindsDB

39.8k
Minds Platform is a general-purpose AI workspace for knowledge workers to automate multi-step tasks and build internal AI tools.
Database ManagementArtificial IntelligenceAI RAG Systems+4
Open Core
Details
AppFlowy logo

AppFlowy

78.5k
AppFlowy is an open-source AI workspace and Notion alternative for notes, databases, collaboration, and building custom productivity apps.
Task ManagementCopyleft LicenseNote Taking Apps+4
Single ContainerFOSS with Issues
Details
LobeHub logo

LobeHub

84.4k
LobeHub is a self-hostable platform for organizing, creating, and collaborating with AI agents as a team.
Artificial IntelligenceGenerative AISelf Hosting Solutions+4
10 Containers
Details
Solo logo

Solo

Free project management app created for freelancers. Create contacts, manage tasks, upload files, track project progress, and keep notes.
Dev Project ManagementTask ManagementNote Taking Apps+1
Details
NoteDiscovery logo

NoteDiscovery

2.9k
Markdown note-taking app with plain-file storage, graph view, and MCP integration (alternative to Obsidian, Notion).
Note Taking AppsKnowledge Management
Details
n8n logo

n8n

209.9k
n8n is a fair-code platform for building and deploying AI agents and workflow automations, with a visual editor, custom code, and 1500+ integrations.
Automation ToolsPermissive LicenseLow Code Development+5
Single ContainerSource AvailableNot FOSS
Details