Try describing what you need:

Fluxer logo

Fluxer

★ 10.8k
GNU Affero General Public License v3.0Open Source — No Paywall21 Containers — 21 containers — heavy hosting footprintBinary · Package · FlatpakNeeds Postgres1 documented maintainer

About

Fluxer is an open source instant messaging and VoIP application aimed at friends, groups, and communities. The README presents it as a cross-platform chat product with downloads for Windows, macOS,...

Community Ratings

No ratings yet

Features

  • instant messaging
  • VoIP chat
  • browser-based access
  • self-hosting
  • desktop and mobile apps
  • stable and canary release channels

Details

Last Updated
Nov 10, 2026
Created
Jan 1, 2026
Install Methods
binarypackage-managerflatpak
Requirements
Windows, macOS, or LinuxAndroid or iOS for mobile apps
Runtime / Stack
Docker
Privacy & Independence
Cloud: Optional
User Model
Multi-User
Deployment

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

Run Fluxer 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 Fluxer with Docker Compose

Compose file from the Fluxer project repository.

21 services · database: postgres · infra: caddy, postgres, valkey, nats, meilisearch · 14 unclassified (seaweedfs, livekit-server, fluxer-api, fluxer-gateway, fluxer-media-proxy, fluxer-push, fluxer-static, fluxer-app-proxy-self-hosted, fluxer-snowflakes, fluxer-users, fluxer-gifs, fluxer-messages, fluxer-unfurl, fluxer-admin)

≈19.3 GB RAM requested (from Docker memory limits)

needs persistent storage (11 volumes)

fluxer_rexctl/internal/stack/files/docker-compose.yml · @ e176053 · scanned Nov 3, 2026

name: fluxer

x-fluxer-settings: ${FLUXER_SETTINGS_LOADED:?rexctl manages this instance. Run rexctl commands here, or run rexctl eject to use docker compose}

x-fluxer-postgres-env: &fluxer-postgres-env
  FLUXER_DATABASE_BACKEND: postgres
  FLUXER_POSTGRES_HOST: ${FLUXER_POSTGRES_HOST:-postgres}
  FLUXER_POSTGRES_PORT: ${FLUXER_POSTGRES_PORT:-}
  FLUXER_POSTGRES_DATABASE: ${FLUXER_POSTGRES_DATABASE:-}
  FLUXER_POSTGRES_USERNAME: ${FLUXER_POSTGRES_USERNAME:-}
  FLUXER_POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-}
  FLUXER_POSTGRES_URL: ${FLUXER_POSTGRES_URL:-}
  FLUXER_POSTGRES_SSL: ${FLUXER_POSTGRES_SSL:-}
  FLUXER_POSTGRES_SSL_CA: ${FLUXER_POSTGRES_SSL_CA:-}
  FLUXER_POSTGRES_PREPARED_STATEMENTS: ${FLUXER_POSTGRES_PREPARED_STATEMENTS:-}
  FLUXER_POSTGRES_KV_TABLE: ${FLUXER_POSTGRES_KV_TABLE:-}

x-fluxer-env: &fluxer-env
  <<: *fluxer-postgres-env
  FLUXER_ENV: production
  LOG_LEVEL: ${LOG_LEVEL:-}
  RUST_LOG: ${RUST_LOG:-}
  HTTP_PROXY: ${HTTP_PROXY:-}
  HTTPS_PROXY: ${HTTPS_PROXY:-}
  NO_PROXY: localhost,127.0.0.1,::1,edge,postgres,valkey,nats,meilisearch,seaweedfs,livekit,api,worker,gateway,media-proxy,push,static-proxy,app-proxy,snowflakes,users,gifs,messages,unfurl,admin${NO_PROXY:+,${NO_PROXY:-}}
  http_proxy: ${HTTP_PROXY:-}
  https_proxy: ${HTTPS_PROXY:-}
  no_proxy: localhost,127.0.0.1,::1,edge,postgres,valkey,nats,meilisearch,seaweedfs,livekit,api,worker,gateway,media-proxy,push,static-proxy,app-proxy,snowflakes,users,gifs,messages,unfurl,admin${NO_PROXY:+,${NO_PROXY:-}}
  FLUXER_SELF_HOSTED: "true"
  FLUXER_BASE_DOMAIN: ${FLUXER_DOMAIN:-}
  FLUXER_PUBLIC_SCHEME: ${FLUXER_PUBLIC_SCHEME:-https}
  FLUXER_PUBLIC_PORT: ${FLUXER_PUBLIC_PORT:-443}
  FLUXER_PUBLIC_ORIGIN: ${FLUXER_PUBLIC_ORIGIN:-}
  FLUXER_TRUST_CLIENT_IP_HEADER: "${FLUXER_TRUST_CLIENT_IP_HEADER:-true}"
  FLUXER_CLIENT_IP_HEADER_NAME: ${FLUXER_CLIENT_IP_HEADER_NAME:-}
  FLUXER_API_HEADERS_TIMEOUT_MS: ${FLUXER_API_HEADERS_TIMEOUT_MS:-}
  FLUXER_API_REQUEST_TIMEOUT_MS: ${FLUXER_API_REQUEST_TIMEOUT_MS:-}
  FLUXER_API_MAX_INFLIGHT_REQUESTS: ${FLUXER_API_MAX_INFLIGHT_REQUESTS:-}
  FLUXER_API_IP_BAN_EXEMPT_IPS: ${FLUXER_API_IP_BAN_EXEMPT_IPS:-}
  FLUXER_IP_BAN_REFRESH_INTERVAL_MS: ${FLUXER_IP_BAN_REFRESH_INTERVAL_MS:-}
  FLUXER_APP_ORIGIN_ALIASES: ${FLUXER_APP_ORIGIN_ALIASES:-}
  FLUXER_BREACHED_PASSWORD_CHECK_ENABLED: ${FLUXER_BREACHED_PASSWORD_CHECK_ENABLED:-}
  FLUXER_BLOCKLIST_FEEDS_ENABLED: ${FLUXER_BLOCKLIST_FEEDS_ENABLED:-}
  FLUXER_GEOIP_DB_PATH: ${FLUXER_GEOIP_DB_PATH:-}

  FLUXER_API_ENDPOINT: ${FLUXER_API_ENDPOINT:-}
  FLUXER_API_CLIENT_ENDPOINT: ${FLUXER_API_CLIENT_ENDPOINT:-}
  FLUXER_APP_ENDPOINT: ${FLUXER_APP_ENDPOINT:-${FLUXER_PUBLIC_ORIGIN:-${FLUXER_PUBLIC_SCHEME:-https}://${FLUXER_DOMAIN:-}}}
  FLUXER_GATEWAY_ENDPOINT: ${FLUXER_GATEWAY_ENDPOINT:-}
  FLUXER_MEDIA_ENDPOINT: ${FLUXER_MEDIA_ENDPOINT:-${FLUXER_PUBLIC_ORIGIN:-${FLUXER_PUBLIC_SCHEME:-https}://${FLUXER_DOMAIN:-}}/media}
  FLUXER_MEDIA_PROXY_PUBLIC_ENDPOINT: ${FLUXER_MEDIA_PROXY_PUBLIC_ENDPOINT:-${FLUXER_MEDIA_ENDPOINT:-${FLUXER_PUBLIC_ORIGIN:-${FLUXER_PUBLIC_SCHEME:-https}://${FLUXER_DOMAIN:-}}/media}}
  FLUXER_STATIC_CDN_ENDPOINT: ${FLUXER_STATIC_CDN_ENDPOINT:-}
  FLUXER_ADMIN_ENDPOINT: ${FLUXER_ADMIN_ENDPOINT:-${FLUXER_PUBLIC_ORIGIN:-${FLUXER_PUBLIC_SCHEME:-https}://${FLUXER_DOMAIN:-}}${FLUXER_ADMIN_BASE_PATH:-/admin}}
  FLUXER_MARKETING_ENDPOINT: ${FLUXER_MARKETING_ENDPOINT:-${FLUXER_PUBLIC_ORIGIN:-${FLUXER_PUBLIC_SCHEME:-https}://${FLUXER_DOMAIN:-}}}
  FLUXER_INVITE_ENDPOINT: ${FLUXER_INVITE_ENDPOINT:-}
  FLUXER_GIFT_ENDPOINT: ${FLUXER_GIFT_ENDPOINT:-}
  FLUXER_STATIC_CDN_DOMAIN: ${FLUXER_STATIC_CDN_DOMAIN:-}
  FLUXER_INVITE_DOMAIN: ${FLUXER_INVITE_DOMAIN:-}
  FLUXER_GIFT_DOMAIN: ${FLUXER_GIFT_DOMAIN:-}

  FLUXER_KV_URL: ${FLUXER_KV_URL:-redis://valkey:6379/0}
  FLUXER_KV_MODE: ${FLUXER_KV_MODE:-}
  FLUXER_NATS_URL: ${FLUXER_NATS_URL:-nats://nats:4222}
  FLUXER_NATS_JETSTREAM_URL: ${FLUXER_NATS_JETSTREAM_URL:-${FLUXER_NATS_URL:-nats://nats:4222}}
  FLUXER_NATS_AUTH_TOKEN: ${FLUXER_NATS_AUTH_TOKEN:-}
  FLUXER_SVC_NATS_URL: ${FLUXER_SVC_NATS_URL:-${FLUXER_NATS_URL:-nats://nats:4222}}
  FLUXER_SVC_SHARD_COUNT: "1"
  FLUXER_SVC_MAX_CONCURRENT_REQUESTS: ${FLUXER_SVC_MAX_CONCURRENT_REQUESTS:-}
  FLUXER_SVC_CACHE_MAX_ENTRIES: ${FLUXER_SVC_CACHE_MAX_ENTRIES:-}
  FLUXER_SVC_CACHE_TTL_MS: ${FLUXER_SVC_CACHE_TTL_MS:-}
  FLUXER_GIF_SERVICE_NATS_CLIENT_NAME: ${FLUXER_GIF_SERVICE_NATS_CLIENT_NAME:-}
  FLUXER_GIF_SERVICE_TIMEOUT_MS: ${FLUXER_GIF_SERVICE_TIMEOUT_MS:-}
  FLUXER_GIF_SERVICE_REGISTER_SHARE_TIMEOUT_MS: ${FLUXER_GIF_SERVICE_REGISTER_SHARE_TIMEOUT_MS:-}
  FLUXER_USERS_SERVICE_NATS_CLIENT_NAME: ${FLUXER_USERS_SERVICE_NATS_CLIENT_NAME:-}
  FLUXER_USERS_SERVICE_TIMEOUT_MS: ${FLUXER_USERS_SERVICE_TIMEOUT_MS:-}
  FLUXER_USERS_SERVICE_INFLIGHT_MAX_ENTRIES: ${FLUXER_USERS_SERVICE_INFLIGHT_MAX_ENTRIES:-}
  FLUXER_SNOWFLAKE_SERVICE_NATS_CLIENT_NAME: ${FLUXER_SNOWFLAKE_SERVICE_NATS_CLIENT_NAME:-}
  FLUXER_SNOWFLAKE_SERVICE_BATCH_SIZE: ${FLUXER_SNOWFLAKE_SERVICE_BATCH_SIZE:-}
  FLUXER_SNOWFLAKE_SERVICE_LOW_WATERMARK: ${FLUXER_SNOWFLAKE_SERVICE_LOW_WATERMARK:-}
  FLUXER_SNOWFLAKE_SERVICE_MAX_BUFFER_AGE_MS: ${FLUXER_SNOWFLAKE_SERVICE_MAX_BUFFER_AGE_MS:-}
  FLUXER_SNOWFLAKE_SERVICE_REQUEST_TIMEOUT_MS: ${FLUXER_SNOWFLAKE_SERVICE_REQUEST_TIMEOUT_MS:-}

  FLUXER_SEARCH_ENGINE: ${FLUXER_SEARCH_ENGINE:-meilisearch}
  FLUXER_SEARCH_URL: ${FLUXER_SEARCH_URL:-http://meilisearch:7700}
  FLUXER_SEARCH_API_KEY: ${MEILI_MASTER_KEY:-}
  FLUXER_SEARCH_USERNAME: ${FLUXER_SEARCH_USERNAME:-}
  FLUXER_SEARCH_PASSWORD: ${FLUXER_SEARCH_PASSWORD:-}
  FLUXER_SEARCH_TLS_REJECT_UNAUTHORIZED: ${FLUXER_SEARCH_TLS_REJECT_UNAUTHORIZED:-}

  FLUXER_S3_ENDPOINT: ${FLUXER_S3_ENDPOINT:-http://seaweedfs:8333}
  FLUXER_S3_PUBLIC_ENDPOINT: ${FLUXER_S3_PUBLIC_ENDPOINT:-${FLUXER_S3_ENDPOINT:-http://seaweedfs:8333}}
  FLUXER_S3_REGION: ${FLUXER_S3_REGION:-us-east-1}
  FLUXER_S3_ACCESS_KEY_ID: ${FLUXER_S3_ACCESS_KEY:-}
  FLUXER_S3_SECRET_ACCESS_KEY: ${FLUXER_S3_SECRET_KEY:-}
  FLUXER_S3_FORCE_PATH_STYLE: "${FLUXER_S3_FORCE_PATH_STYLE:-true}"
  FLUXER_S3_BUCKET_CDN: ${FLUXER_S3_BUCKET_CDN:-fluxer}
  FLUXER_S3_BUCKET_UPLOADS: ${FLUXER_S3_BUCKET_UPLOADS:-fluxer-uploads}
  FLUXER_S3_BUCKET_REPORTS: ${FLUXER_S3_BUCKET_REPORTS:-fluxer-reports}
  FLUXER_S3_BUCKET_HARVESTS: ${FLUXER_S3_BUCKET_HARVESTS:-fluxer-harvests}
  FLUXER_API_PRESIGNED_HARVEST_DOWNLOADS_ENABLED: "${FLUXER_API_PRESIGNED_HARVEST_DOWNLOADS_ENABLED:-false}"
  FLUXER_API_STORAGE_CHANGE_FEED_ENABLED: ${FLUXER_API_STORAGE_CHANGE_FEED_ENABLED:-}
  FLUXER_API_STORAGE_CHANGE_FEED_STREAM: ${FLUXER_API_STORAGE_CHANGE_FEED_STREAM:-}
  FLUXER_API_STORAGE_CHANGE_FEED_SKIP_BUCKETS: ${FLUXER_API_STORAGE_CHANGE_FEED_SKIP_BUCKETS:-}
  FLUXER_CACHE_PURGE_ADAPTER: ${FLUXER_CACHE_PURGE_ADAPTER:-}
  FLUXER_CACHE_PURGE_HTTP_ENDPOINT: ${FLUXER_CACHE_PURGE_HTTP_ENDPOINT:-}
  FLUXER_CACHE_PURGE_HTTP_TOKEN: ${FLUXER_CACHE_PURGE_HTTP_TOKEN:-}
  FLUXER_CACHE_PURGE_HTTP_TIMEOUT_MS: ${FLUXER_CACHE_PURGE_HTTP_TIMEOUT_MS:-}
  FLUXER_CACHE_PURGE_CONTROLLER_ENDPOINT: ${FLUXER_CACHE_PURGE_CONTROLLER_ENDPOINT:-}
  FLUXER_CACHE_PURGE_CONTROLLER_TOKEN: ${FLUXER_CACHE_PURGE_CONTROLLER_TOKEN:-}
  FLUXER_CACHE_PURGE_CONTROLLER_TIMEOUT_MS: ${FLUXER_CACHE_PURGE_CONTROLLER_TIMEOUT_MS:-}

  FLUXER_LIVEKIT_ENABLED: "${FLUXER_LIVEKIT_ENABLED:-true}"
  FLUXER_LIVEKIT_API_KEY: ${LIVEKIT_API_KEY:-}
  FLUXER_LIVEKIT_API_SECRET: ${LIVEKIT_API_SECRET:-}
  FLUXER_LIVEKIT_INTERNAL_URL: ${FLUXER_LIVEKIT_INTERNAL_URL:-http://livekit:7880}
  FLUXER_LIVEKIT_DEFAULT_REGION: '${FLUXER_LIVEKIT_DEFAULT_REGION:-{"id":"default","name":"Default","emoji":"🌍","latitude":0,"longitude":0}}'
  FLUXER_LIVEKIT_URL: ${FLUXER_LIVEKIT_URL:-${FLUXER_PUBLIC_ORIGIN:-${FLUXER_PUBLIC_SCHEME:-https}://${FLUXER_DOMAIN:-}:${FLUXER_PUBLIC_PORT:-443}}/livekit}
  FLUXER_VOICE_P2P_STUN_URLS: ${FLUXER_VOICE_P2P_STUN_URLS:-}

  FLUXER_KLIPY_API_KEY: ${FLUXER_KLIPY_API_KEY:-}
  FLUXER_YOUTUBE_API_KEY: ${FLUXER_YOUTUBE_API_KEY:-}
  FLUXER_API_UNFURL_IGNORED_HOSTS: ${FLUXER_API_UNFURL_IGNORED_HOSTS:-}

  FLUXER_EMAIL_ENABLED: ${FLUXER_EMAIL_ENABLED:-}
  FLUXER_EMAIL_PROVIDER: ${FLUXER_EMAIL_PROVIDER:-}
  FLUXER_EMAIL_DELIVERY: ${FLUXER_EMAIL_DELIVERY:-}
  FLUXER_EMAIL_FROM_EMAIL: ${FLUXER_EMAIL_FROM_EMAIL:-noreply@${FLUXER_DOMAIN:-}}
  FLUXER_EMAIL_FROM_NAME: ${FLUXER_EMAIL_FROM_NAME:-}
  FLUXER_EMAIL_REPLY_TO_EMAIL: ${FLUXER_EMAIL_REPLY_TO_EMAIL:-}
  FLUXER_EMAIL_APP_BASE_URL: ${FLUXER_EMAIL_APP_BASE_URL:-}
  FLUXER_EMAIL_WEBHOOK_SECRET: ${FLUXER_EMAIL_WEBHOOK_SECRET:-}
  FLUXER_EMAIL_SMTP_HOST: ${FLUXER_EMAIL_SMTP_HOST:-}
  FLUXER_EMAIL_SMTP_PORT: ${FLUXER_EMAIL_SMTP_PORT:-}
  FLUXER_EMAIL_SMTP_USERNAME: ${FLUXER_EMAIL_SMTP_USERNAME:-}
  FLUXER_EMAIL_SMTP_PASSWORD: ${FLUXER_EMAIL_SMTP_PASSWORD:-}
  FLUXER_EMAIL_SMTP_TLS_MODE: ${FLUXER_EMAIL_SMTP_TLS_MODE:-}
  FLUXER_EMAIL_SMTP_SECURE: ${FLUXER_EMAIL_SMTP_SECURE:-}
  FLUXER_EMAIL_FALLBACK_DOMAINS: ${FLUXER_EMAIL_FALLBACK_DOMAINS:-}
  FLUXER_EMAIL_FALLBACK_SMTP_HOST: ${FLUXER_EMAIL_FALLBACK_SMTP_HOST:-}
  FLUXER_EMAIL_FALLBACK_SMTP_PORT: ${FLUXER_EMAIL_FALLBACK_SMTP_PORT:-}
  FLUXER_EMAIL_FALLBACK_SMTP_USERNAME: ${FLUXER_EMAIL_FALLBACK_SMTP_USERNAME:-}
  FLUXER_EMAIL_FALLBACK_SMTP_PASSWORD: ${FLUXER_EMAIL_FALLBACK_SMTP_PASSWORD:-}
  FLUXER_EMAIL_FALLBACK_SMTP_TLS_MODE: ${FLUXER_EMAIL_FALLBACK_SMTP_TLS_MODE:-}
  FLUXER_EMAIL_FALLBACK_SMTP_HEADERS: ${FLUXER_EMAIL_FALLBACK_SMTP_HEADERS:-}

  FLUXER_STRIPE_ENABLED: ${FLUXER_STRIPE_ENABLED:-}
  FLUXER_STRIPE_SECRET_KEY: ${FLUXER_STRIPE_SECRET_KEY:-}
  FLUXER_STRIPE_WEBHOOK_SECRET: ${FLUXER_STRIPE_WEBHOOK_SECRET:-}
  FLUXER_STRIPE_PRICES: ${FLUXER_STRIPE_PRICES:-}
  FLUXER_STRIPE_LEGACY_PRICES: ${FLUXER_STRIPE_LEGACY_PRICES:-}
  FLUXER_API_DONATION_PROXY_KEY: ${FLUXER_API_DONATION_PROXY_KEY:-}
  FLUXER_API_TRUSTED_CALLERS: ${FLUXER_API_TRUSTED_CALLERS:-}
  FLUXER_VISIONARIES_GUILD_ID: ${FLUXER_VISIONARIES_GUILD_ID:-}
  FLUXER_VISIONARIES_GUILD_VISIONARY_ROLE_ID: ${FLUXER_VISIONARIES_GUILD_VISIONARY_ROLE_ID:-}

  FLUXER_CLAMAV_ENABLED: ${FLUXER_CLAMAV_ENABLED:-}
  FLUXER_CLAMAV_HOST: ${FLUXER_CLAMAV_HOST:-}
  FLUXER_CLAMAV_PORT: ${FLUXER_CLAMAV_PORT:-}
  FLUXER_CLAMAV_FAIL_OPEN: ${FLUXER_CLAMAV_FAIL_OPEN:-}

  FLUXER_APP_PRODUCT_NAME: ${FLUXER_APP_PRODUCT_NAME:-}
  FLUXER_APP_ICON_URL: ${FLUXER_APP_ICON_URL:-}
  FLUXER_APP_SYMBOL_URL: ${FLUXER_APP_SYMBOL_URL:-}
  FLUXER_APP_LOGO_URL: ${FLUXER_APP_LOGO_URL:-}
  FLUXER_APP_WORDMARK_URL: ${FLUXER_APP_WORDMARK_URL:-}
  FLUXER_APP_FAVICON_URL: ${FLUXER_APP_FAVICON_URL:-}
  FLUXER_APP_FAVICON_DARK_URL: ${FLUXER_APP_FAVICON_DARK_URL:-}
  FLUXER_APP_SIGN_IN_BACKDROP_URL: ${FLUXER_APP_SIGN_IN_BACKDROP_URL:-}
  FLUXER_APP_THEME_COLOR: ${FLUXER_APP_THEME_COLOR:-}
  FLUXER_APP_STATUS_PAGE_URL: ${FLUXER_APP_STATUS_PAGE_URL:-}
  FLUXER_APP_STATUS_PAGE_INCIDENT_HISTORY_URL: ${FLUXER_APP_STATUS_PAGE_INCIDENT_HISTORY_URL:-}
  FLUXER_INSTANCE_SETUP_CONFIGURED: ${FLUXER_INSTANCE_SETUP_CONFIGURED:-}
  FLUXER_ACCOUNT_IDENTITY: ${FLUXER_ACCOUNT_IDENTITY:-}
  FLUXER_TAG_STYLE: ${FLUXER_TAG_STYLE:-}
  FLUXER_AUTO_JOIN_INVITE_CODE: ${FLUXER_AUTO_JOIN_INVITE_CODE:-}
  FLUXER_DISCOVERY_ENABLED: ${FLUXER_DISCOVERY_ENABLED:-}
  FLUXER_DISCOVERY_MIN_MEMBER_COUNT: ${FLUXER_DISCOVERY_MIN_MEMBER_COUNT:-}
  FLUXER_DELETION_GRACE_PERIOD_HOURS: ${FLUXER_DELETION_GRACE_PERIOD_HOURS:-}
  FLUXER_REPORT_RETENTION_DAYS: ${FLUXER_REPORT_RETENTION_DAYS:-}
  FLUXER_REPORT_RETENTION_DRY_RUN: ${FLUXER_REPORT_RETENTION_DRY_RUN:-}
  FLUXER_API_WORKER_LANE_CONCURRENCY_OVERRIDES: ${FLUXER_API_WORKER_LANE_CONCURRENCY_OVERRIDES:-}

  FLUXER_AUTH_BLUESKY_ENABLED: ${FLUXER_AUTH_BLUESKY_ENABLED:-}
  FLUXER_AUTH_BLUESKY_CLIENT_NAME: ${FLUXER_AUTH_BLUESKY_CLIENT_NAME:-}
  FLUXER_AUTH_BLUESKY_CLIENT_URI: ${FLUXER_AUTH_BLUESKY_CLIENT_URI:-}
  FLUXER_AUTH_BLUESKY_LOGO_URI: ${FLUXER_AUTH_BLUESKY_LOGO_URI:-}
  FLUXER_AUTH_BLUESKY_TOS_URI: ${FLUXER_AUTH_BLUESKY_TOS_URI:-}
  FLUXER_AUTH_BLUESKY_POLICY_URI: ${FLUXER_AUTH_BLUESKY_POLICY_URI:-}
  FLUXER_AUTH_BLUESKY_KEYS: ${FLUXER_AUTH_BLUESKY_KEYS:-}

  FLUXER_PUSH_APNS_ENABLED: ${FLUXER_PUSH_APNS_ENABLED:-}
  FLUXER_PUSH_APNS_TEAM_ID: ${FLUXER_PUSH_APNS_TEAM_ID:-}
  FLUXER_PUSH_APNS_KEY_ID: ${FLUXER_PUSH_APNS_KEY_ID:-}
  FLUXER_PUSH_APNS_PRIVATE_KEY: ${FLUXER_PUSH_APNS_PRIVATE_KEY:-}
  FLUXER_PUSH_APNS_PRIVATE_KEY_PATH: ${FLUXER_PUSH_APNS_PRIVATE_KEY_PATH:-}
  FLUXER_PUSH_APNS_APPS: ${FLUXER_PUSH_APNS_APPS:-}

  FLUXER_SUDO_MODE_SECRET: ${FLUXER_SUDO_MODE_SECRET:-}
  FLUXER_CONNECTION_INITIATION_SECRET: ${FLUXER_CONNECTION_INITIATION_SECRET:-}
  FLUXER_PROFILE_PSEUDONYM_SECRET: ${FLUXER_PROFILE_PSEUDONYM_SECRET:-}
  FLUXER_SSO_ALLOW_PRIVATE_ADDRESSES: ${FLUXER_SSO_ALLOW_PRIVATE_ADDRESSES:-}
  FLUXER_VAPID_PUBLIC_KEY: ${FLUXER_VAPID_PUBLIC_KEY:-}
  FLUXER_VAPID_PRIVATE_KEY: ${FLUXER_VAPID_PRIVATE_KEY:-}
  FLUXER_VAPID_EMAIL: ${FLUXER_VAPID_EMAIL:-admin@${FLUXER_DOMAIN:-}}
  FLUXER_PASSKEY_RP_ID: ${FLUXER_PASSKEY_RP_ID:-${FLUXER_DOMAIN:-}}
  FLUXER_PASSKEY_RP_NAME: ${FLUXER_PASSKEY_RP_NAME:-}
  FLUXER_PASSKEY_ADDITIONAL_ALLOWED_ORIGINS: ${FLUXER_PASSKEY_ADDITIONAL_ALLOWED_ORIGINS:-${FLUXER_PUBLIC_ORIGIN:-${FLUXER_PUBLIC_SCHEME:-https}://${FLUXER_DOMAIN:-}}}
  FLUXER_GATEWAY_RPC_AUTH_TOKEN: ${FLUXER_GATEWAY_RPC_AUTH_TOKEN:-}
  FLUXER_MEDIA_PROXY_SECRET_KEY: ${FLUXER_MEDIA_PROXY_SECRET_KEY:-}
  FLUXER_MEDIA_PROXY_UPLOAD_RELAY_SECRET_BASE64: ${FLUXER_MEDIA_PROXY_UPLOAD_RELAY_SECRET_BASE64:-}
  FLUXER_MEDIA_PROXY_ATTACHMENT_URL_SECRETS_BASE64: ${FLUXER_MEDIA_PROXY_ATTACHMENT_URL_SECRETS_BASE64:-}
  FLUXER_ADMIN_SECRET_KEY_BASE: ${FLUXER_ADMIN_SECRET_KEY_BASE:-}
  FLUXER_ADMIN_OAUTH_CLIENT_SECRET: ${FLUXER_ADMIN_OAUTH_CLIENT_SECRET:-}

  FLUXER_INTERNAL_API_ENDPOINT: http://api:8080
  FLUXER_INTERNAL_MEDIA_PROXY_ENDPOINT: http://media-proxy:8080
  FLUXER_MEDIA_PROXY_UPLOAD_RELAY_ENDPOINT: ${FLUXER_MEDIA_PROXY_UPLOAD_RELAY_ENDPOINT:-${FLUXER_PUBLIC_ORIGIN:-${FLUXER_PUBLIC_SCHEME:-https}://${FLUXER_DOMAIN:-}}/media}
  FLUXER_MEDIA_PROXY_UPLOAD_RELAY_MAX_BODY_BYTES: ${FLUXER_MEDIA_PROXY_UPLOAD_RELAY_MAX_BODY_BYTES:-}
  FLUXER_MEDIA_PROXY_UPLOAD_RELAY_TOKEN_TTL_SECS: ${FLUXER_MEDIA_PROXY_UPLOAD_RELAY_TOKEN_TTL_SECS:-}
  FLUXER_MEDIA_PROXY_UPLOAD_RELAY_KEEP_DIRECT_COUNTRIES: ${FLUXER_MEDIA_PROXY_UPLOAD_RELAY_KEEP_DIRECT_COUNTRIES:-}

x-fluxer-rust-env: &fluxer-rust-env
  <<: *fluxer-env
  TOKIO_WORKER_THREADS: ${FLUXER_TOKIO_WORKER_THREADS:-2}
  MALLOC_ARENA_MAX: ${FLUXER_MALLOC_ARENA_MAX:-2}

x-fluxer-logging: &fluxer-logging
  driver: json-file
  options:
    max-size: ${FLUXER_LOG_MAX_SIZE:-10m}
    max-file: "${FLUXER_LOG_MAX_FILE:-3}"

x-fluxer-service: &fluxer-service
  restart: ${FLUXER_RESTART_POLICY:-unless-stopped}
  networks: [fluxer]
  logging: *fluxer-logging

x-fluxer-healthcheck: &fluxer-healthcheck
  interval: ${FLUXER_HEALTHCHECK_INTERVAL:-10s}
  timeout: ${FLUXER_HEALTHCHECK_TIMEOUT:-5s}
  retries: ${FLUXER_HEALTHCHECK_RETRIES:-10}
  start_period: ${FLUXER_HEALTHCHECK_START_PERIOD:-60s}
  start_interval: ${FLUXER_HEALTHCHECK_START_INTERVAL:-1s}

x-fluxer-app-healthcheck: &fluxer-app-healthcheck
  interval: ${FLUXER_HEALTHCHECK_INTERVAL:-10s}
  timeout: ${FLUXER_HEALTHCHECK_TIMEOUT:-5s}
  retries: ${FLUXER_APP_HEALTHCHECK_RETRIES:-30}
  start_period: ${FLUXER_APP_HEALTHCHECK_START_PERIOD:-90s}
  start_interval: ${FLUXER_HEALTHCHECK_START_INTERVAL:-1s}

x-fluxer-svc-healthcheck: &fluxer-svc-healthcheck
  <<: *fluxer-app-healthcheck
  start_period: ${FLUXER_SVC_HEALTHCHECK_START_PERIOD:-60s}
  test: ["CMD-SHELL", "bash -c 'exec 3<>/dev/tcp/127.0.0.1/8090 && printf \"GET /_health HTTP/1.0\\r\\n\\r\\n\" >&3 && head -n 1 <&3 | grep -q \" 200 \"'"]

services:
  edge:
    <<: *fluxer-service
    image: ${FLUXER_CADDY_IMAGE:-caddy:2.11-alpine}
    deploy:
      resources:
        limits:
          memory: ${FLUXER_CADDY_MEMORY_LIMIT:-384mb}
    ports:
      - "${FLUXER_HTTP_PORT:-80}:80"
      - "${FLUXER_HTTPS_PORT:-443}:443"
      - "${FLUXER_HTTPS_PORT:-443}:443/udp"
    environment:
      FLUXER_EDGE_SITE_ADDRESS: ${FLUXER_EDGE_SITE_ADDRESS:-${FLUXER_CADDY_SITE_ADDRESS:-${FLUXER_PUBLIC_SCHEME:-https}://${FLUXER_DOMAIN:-}}}
      FLUXER_EDGE_TRUSTED_PROXIES: ${FLUXER_EDGE_TRUSTED_PROXIES:-127.0.0.1/32}
      FLUXER_EDGE_ENCODE: ${FLUXER_EDGE_ENCODE:-zstd gzip}
      FLUXER_EDGE_PROTOCOLS: ${FLUXER_EDGE_PROTOCOLS:-h1 h2 h3}
      FLUXER_EDGE_GLOBAL_OPTIONS: ${FLUXER_EDGE_GLOBAL_OPTIONS:-}
      FLUXER_EDGE_TLS_DIRECTIVE: ${FLUXER_EDGE_TLS:+tls ${FLUXER_EDGE_TLS:-}}
      FLUXER_ADMIN_BASE_PATH: ${FLUXER_ADMIN_BASE_PATH:-/admin}
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - ./certs:/certs:ro
      - ./caddy.d:/etc/caddy/conf.d:ro
      - edge-data:/data
      - edge-config:/config
    healthcheck:
      test: ["CMD", "wget", "-q", "-O", "/dev/null", "http://127.0.0.1:2019/config/"]
      <<: *fluxer-healthcheck
    depends_on:
      api: {condition: service_healthy}
      gateway: {condition: service_healthy}

  postgres:
    <<: *fluxer-service
    image: ${FLUXER_POSTGRES_IMAGE:-postgres:16-alpine}
    deploy:
      resources:
        limits:
          memory: ${FLUXER_POSTGRES_MEMORY_LIMIT:-5gb}
        reservations:
          memory: ${FLUXER_POSTGRES_MEMORY_RESERVATION:-3gb}
    command: >
      postgres
      -c max_connections=${FLUXER_POSTGRES_SERVER_MAX_CONNECTIONS:-150}
      -c shared_buffers=${FLUXER_POSTGRES_SHARED_BUFFERS:-512MB}
      -c effective_cache_size=${FLUXER_POSTGRES_EFFECTIVE_CACHE_SIZE:-2GB}
      -c work_mem=${FLUXER_POSTGRES_WORK_MEM:-8MB}
      -c maintenance_work_mem=${FLUXER_POSTGRES_MAINTENANCE_WORK_MEM:-256MB}
      -c autovacuum_work_mem=${FLUXER_POSTGRES_AUTOVACUUM_WORK_MEM:-128MB}
      -c random_page_cost=${FLUXER_POSTGRES_RANDOM_PAGE_COST:-1.1}
      -c effective_io_concurrency=${FLUXER_POSTGRES_EFFECTIVE_IO_CONCURRENCY:-200}
      -c default_statistics_target=${FLUXER_POSTGRES_DEFAULT_STATISTICS_TARGET:-200}
      -c jit=${FLUXER_POSTGRES_JIT:-off}
      -c min_wal_size=${FLUXER_POSTGRES_MIN_WAL_SIZE:-80MB}
      -c max_wal_size=${FLUXER_POSTGRES_MAX_WAL_SIZE:-2GB}
      -c checkpoint_completion_target=${FLUXER_POSTGRES_CHECKPOINT_COMPLETION_TARGET:-0.9}
      -c wal_buffers=${FLUXER_POSTGRES_WAL_BUFFERS:-16MB}
      -c wal_compression=${FLUXER_POSTGRES_WAL_COMPRESSION:-zstd}
      -c bgwriter_delay=${FLUXER_POSTGRES_BGWRITER_DELAY:-50ms}
      -c bgwriter_lru_maxpages=${FLUXER_POSTGRES_BGWRITER_LRU_MAXPAGES:-1000}
      -c autovacuum_vacuum_scale_factor=${FLUXER_POSTGRES_AUTOVACUUM_VACUUM_SCALE_FACTOR:-0.05}
      -c autovacuum_analyze_scale_factor=${FLUXER_POSTGRES_AUTOVACUUM_ANALYZE_SCALE_FACTOR:-0.02}
      -c autovacuum_vacuum_cost_limit=${FLUXER_POSTGRES_AUTOVACUUM_VACUUM_COST_LIMIT:-2000}
      -c autovacuum_max_workers=${FLUXER_POSTGRES_AUTOVACUUM_MAX_WORKERS:-3}
      -c max_parallel_workers_per_gather=${FLUXER_POSTGRES_MAX_PARALLEL_WORKERS_PER_GATHER:-2}
      -c max_parallel_workers=${FLUXER_POSTGRES_MAX_PARALLEL_WORKERS:-8}
      -c track_io_timing=${FLUXER_POSTGRES_TRACK_IO_TIMING:-on}
      -c shared_preload_libraries=${FLUXER_POSTGRES_SHARED_PRELOAD_LIBRARIES:-pg_stat_statements}
    shm_size: ${FLUXER_POSTGRES_SHM_SIZE:-1gb}
    environment:
      POSTGRES_DB: ${FLUXER_POSTGRES_DATABASE:-fluxer}
      POSTGRES_USER: ${FLUXER_POSTGRES_USERNAME:-fluxer}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-}
    volumes:
      - postgres-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U \"$$POSTGRES_USER\" -d \"$$POSTGRES_DB\""]
      <<: *fluxer-healthcheck

  valkey:
    <<: *fluxer-service
    image: ${FLUXER_VALKEY_IMAGE:-valkey/valkey:9.1-alpine}
    deploy:
      resources:
        limits:
          memory: ${FLUXER_VALKEY_MEMORY_LIMIT:-256mb}
    command: ["valkey-server", "--appendonly", "yes", "--appendfsync", "${FLUXER_VALKEY_APPENDFSYNC:-everysec}", "--dir", "/data",
              "--save", "${FLUXER_VALKEY_SAVE:-}",
              "--maxmemory", "${FLUXER_VALKEY_MAXMEMORY:-192mb}",
              "--maxmemory-policy", "${FLUXER_VALKEY_MAXMEMORY_POLICY:-noeviction}"]
    volumes:
      - valkey-data:/data
    healthcheck:
      test: ["CMD", "valkey-cli", "ping"]
      <<: *fluxer-healthcheck

  nats:
    <<: *fluxer-service
    image: ${FLUXER_NATS_IMAGE:-nats:2.14-alpine}
    deploy:
      resources:
        limits:
          memory: ${FLUXER_NATS_MEMORY_LIMIT:-256mb}
    environment:
      GOMEMLIMIT: ${FLUXER_NATS_GOMEMLIMIT:-200MiB}
      FLUXER_NATS_MAX_MEMORY_STORE: ${FLUXER_NATS_MAX_MEMORY_STORE:-64MB}
      FLUXER_NATS_MAX_FILE_STORE: ${FLUXER_NATS_MAX_FILE_STORE:-10GB}
    command:
      - sh
      - -c
      - >
        printf 'http_port: 8222\njetstream {\n  store_dir: /data\n  max_memory_store: %s\n  max_file_store: %s\n}\n'
        "$$FLUXER_NATS_MAX_MEMORY_STORE" "$$FLUXER_NATS_MAX_FILE_STORE" > /tmp/nats.conf
        && exec nats-server -c /tmp/nats.conf
    volumes:
      - nats-data:/data
    healthcheck:
      test: ["CMD", "wget", "-q", "-O", "/dev/null", "http://127.0.0.1:8222/healthz"]
      <<: *fluxer-healthcheck

  meilisearch:
    <<: *fluxer-service
    image: ${FLUXER_MEILISEARCH_IMAGE:-getmeili/meilisearch:v1.53}
    deploy:
      resources:
        limits:
          memory: ${FLUXER_MEILISEARCH_MEMORY_LIMIT:-1536mb}
    environment:
      MEILI_ENV: ${FLUXER_MEILISEARCH_ENV:-production}
      MEILI_NO_ANALYTICS: "${FLUXER_MEILISEARCH_NO_ANALYTICS:-true}"
      MEILI_UPGRADE_DB: "true"
      MEILI_MAX_INDEXING_MEMORY: ${FLUXER_MEILISEARCH_MAX_INDEXING_MEMORY:-256mb}
      MEILI_MAX_INDEXING_THREADS: ${FLUXER_MEILISEARCH_MAX_INDEXING_THREADS:-2}
      MEILI_LOG_LEVEL: ${FLUXER_MEILISEARCH_LOG_LEVEL:-WARN}
      MEILI_MASTER_KEY: ${MEILI_MASTER_KEY:-}
    volumes:
      - meilisearch-data:/meili_data
    healthcheck:
      test: ["CMD", "wget", "-q", "-O", "/dev/null", "http://127.0.0.1:7700/health"]
      <<: *fluxer-healthcheck

  seaweedfs:
    <<: *fluxer-service
    image: ${FLUXER_SEAWEEDFS_IMAGE:-chrislusf/seaweedfs:4.47}
    deploy:
      resources:
        limits:
          memory: ${FLUXER_SEAWEEDFS_MEMORY_LIMIT:-2gb}
    environment:
      GOMEMLIMIT: ${FLUXER_SEAWEEDFS_GOMEMLIMIT:-1536MiB}
      WEED_MASTER_VOLUME_GROWTH_COPY_1: ${FLUXER_SEAWEEDFS_VOLUME_GROWTH:-1}
    command: ["server", "-s3", "-s3.iam=false", "-dir=/data", "-master.telemetry=${FLUXER_SEAWEEDFS_TELEMETRY:-false}", "-master.volumeSizeLimitMB=${FLUXER_SEAWEEDFS_VOLUME_SIZE_LIMIT_MB:-1024}", "-master.garbageThreshold=${FLUXER_SEAWEEDFS_GARBAGE_THRESHOLD:-0.1}", "-volume.max=${FLUXER_SEAWEEDFS_VOLUME_MAX:-10000}"]
    volumes:
      - seaweedfs-data:/data
    healthcheck:
      test: ["CMD", "wget", "-q", "-O", "/dev/null", "http://127.0.0.1:8333/healthz"]
      <<: *fluxer-healthcheck
      retries: ${FLUXER_SEAWEEDFS_HEALTHCHECK_RETRIES:-20}
      start_period: ${FLUXER_SEAWEEDFS_HEALTHCHECK_START_PERIOD:-60s}

  seaweedfs-init:
    image: ${FLUXER_SEAWEEDFS_IMAGE:-chrislusf/seaweedfs:4.47}
    deploy:
      resources:
        limits:
          memory: ${FLUXER_SEAWEEDFS_INIT_MEMORY_LIMIT:-128mb}
    networks: [fluxer]
    logging: *fluxer-logging
    depends_on:
      seaweedfs: {condition: service_healthy, restart: true}
    restart: "no"
    environment:
      FLUXER_S3_ACCESS_KEY: ${FLUXER_S3_ACCESS_KEY:-}
      FLUXER_S3_SECRET_KEY: ${FLUXER_S3_SECRET_KEY:-}
      FLUXER_S3_BUCKET_CDN: ${FLUXER_S3_BUCKET_CDN:-fluxer}
      FLUXER_S3_BUCKET_UPLOADS: ${FLUXER_S3_BUCKET_UPLOADS:-fluxer-uploads}
      FLUXER_S3_BUCKET_REPORTS: ${FLUXER_S3_BUCKET_REPORTS:-fluxer-reports}
      FLUXER_S3_BUCKET_HARVESTS: ${FLUXER_S3_BUCKET_HARVESTS:-fluxer-harvests}
      FLUXER_SEAWEEDFS_INIT_ATTEMPTS: ${FLUXER_SEAWEEDFS_INIT_ATTEMPTS:-60}
      FLUXER_SEAWEEDFS_UPLOADS_TTL: ${FLUXER_SEAWEEDFS_UPLOADS_TTL:-1d}
    entrypoint:
      - /bin/sh
      - -c
      - >
        buckets="$$FLUXER_S3_BUCKET_CDN $$FLUXER_S3_BUCKET_UPLOADS $$FLUXER_S3_BUCKET_REPORTS $$FLUXER_S3_BUCKET_HARVESTS";
        missing="$$buckets";
        for attempt in $$(seq 1 $$FLUXER_SEAWEEDFS_INIT_ATTEMPTS); do
          if ! nc -z seaweedfs 9333 2>/dev/null; then
            sleep 2;
            continue;
          fi;
          listed=$$(echo "s3.bucket.list" | timeout 10 weed shell -master=seaweedfs:9333 2>&1);
          missing="";
          failure="";
          for b in $$buckets; do
            echo "$$listed" | grep -q "^[[:space:]]*$$b[[:space:]]" || missing="$${missing:+$$missing }$$b";
          done;
          if [ -z "$$missing" ]; then
            if [ "$$FLUXER_SEAWEEDFS_UPLOADS_TTL" = off ]; then
              ttl_rule="fs.configure -locationPrefix=/buckets/$$FLUXER_S3_BUCKET_UPLOADS/ -delete -apply";
            else
              ttl_rule="fs.configure -locationPrefix=/buckets/$$FLUXER_S3_BUCKET_UPLOADS/ -ttl=$$FLUXER_SEAWEEDFS_UPLOADS_TTL -apply";
            fi;
            failure="could not configure the S3 identity";
            if echo "s3.configure -user=fluxer -access_key=$$FLUXER_S3_ACCESS_KEY -secret_key=$$FLUXER_S3_SECRET_KEY -actions=Admin,Read,Write,List,Tagging -apply" | timeout 10 weed shell -master=seaweedfs:9333 >/dev/null 2>&1; then
              failure="could not set the expiry of $$FLUXER_S3_BUCKET_UPLOADS";
              if echo "$$ttl_rule" | timeout 10 weed shell -master=seaweedfs:9333 >/dev/null 2>&1; then
                echo "buckets ready";
                exit 0;
              fi;
            fi;
            sleep 2;
            continue;
          fi;
          for b in $$missing; do
            echo "s3.bucket.create -name $$b" | timeout 10 weed shell -master=seaweedfs:9333 >/dev/null 2>&1;
          done;
          sleep 2;
        done;
        if [ -n "$$failure" ]; then
          echo "seaweedfs-init $$failure" >&2;
        else
          echo "seaweedfs-init could not verify buckets: $$missing" >&2;
        fi;
        exit 1;

  livekit:
    <<: *fluxer-service
    image: ${FLUXER_LIVEKIT_IMAGE:-livekit/livekit-server:v1.12.0}
    deploy:
      resources:
        limits:
          memory: ${FLUXER_LIVEKIT_MEMORY_LIMIT:-512mb}
    environment:
      GOMEMLIMIT: ${FLUXER_LIVEKIT_GOMEMLIMIT:-435MiB}
      LIVEKIT_KEYS: "${LIVEKIT_API_KEY:-}: ${LIVEKIT_API_SECRET:-}"
      LIVEKIT_CONFIG: |
        port: 7880
        log_level: ${FLUXER_LIVEKIT_LOG_LEVEL:-info}
        rtc:
          tcp_port: ${FLUXER_LIVEKIT_TCP_PORT:-7881}
          udp_port: ${FLUXER_LIVEKIT_UDP_PORT:-7882}
          use_external_ip: ${FLUXER_LIVEKIT_USE_EXTERNAL_IP:-true}
          node_ip: "${FLUXER_LIVEKIT_NODE_IP:-}"
          stun_servers:
            - ${FLUXER_LIVEKIT_STUN_PRIMARY:-stun.l.google.com:19302}
            - ${FLUXER_LIVEKIT_STUN_SECONDARY:-stun1.l.google.com:19302}
        webhook:
          api_key: ${LIVEKIT_API_KEY:-}
          urls:
            - http://api:8080/webhooks/livekit
    ports:
      - "${FLUXER_LIVEKIT_TCP_PORT:-7881}:${FLUXER_LIVEKIT_TCP_PORT:-7881}"
      - "${FLUXER_LIVEKIT_UDP_PORT:-7882}:${FLUXER_LIVEKIT_UDP_PORT:-7882}/udp"
    healthcheck:
      test: ["CMD", "wget", "-q", "-O", "/dev/null", "http://127.0.0.1:7880/"]
      <<: *fluxer-healthcheck

  api:
    <<: *fluxer-service
    image: ${FLUXER_REGISTRY:-ghcr.io/${FLUXER_REGISTRY_OWNER:-fluxerapp}}/fluxer-api:${FLUXER_IMAGE_TAG:-v1}
    deploy:
      resources:
        limits:
          memory: ${FLUXER_API_MEMORY_LIMIT:-2560mb}
        reservations:
          memory: ${FLUXER_API_MEMORY_RESERVATION:-1gb}
    environment:
      <<: *fluxer-env
      FLUXER_API_PORT: "8080"
      FLUXER_SETUP_TOKEN: ${FLUXER_SETUP_TOKEN:-}
      NODE_OPTIONS: --max-old-space-size${FLUXER_API_NODE_HEAP_MB:+=}${FLUXER_API_NODE_HEAP_MB:--percentage=60}${FLUXER_API_NODE_OPTIONS:+ ${FLUXER_API_NODE_OPTIONS:-}}
      NODE_EXTRA_CA_CERTS: ${FLUXER_NODE_EXTRA_CA_CERTS:-/etc/ssl/certs/ca-certificates.crt}
      FLUXER_POSTGRES_MAX_CONNECTIONS: "${FLUXER_API_POSTGRES_MAX_CONNECTIONS:-25}"
      FLUXER_API_PRESIGNED_ATTACHMENT_UPLOADS_ENABLED: "${FLUXER_API_PRESIGNED_ATTACHMENT_UPLOADS_ENABLED:-true}"
      FLUXER_API_EMBEDDED_WORKER: "${FLUXER_API_EMBEDDED_WORKER:-false}"
    healthcheck:
      test: ["CMD-SHELL", "bash -c 'exec 3<>/dev/tcp/127.0.0.1/8080 && printf \"GET /_health HTTP/1.0\\r\\nHost: 127.0.0.1\\r\\n\\r\\n\" >&3 && head -n 1 <&3 | grep -q \" 200 \"'"]
      <<: *fluxer-app-healthcheck
    depends_on:
      postgres: {condition: service_healthy}
      valkey: {condition: service_healthy}
      nats: {condition: service_healthy}
      meilisearch: {condition: service_healthy}
      seaweedfs-init: {condition: service_completed_successfully}
      gifs: {condition: service_healthy}
      snowflakes: {condition: service_healthy}
      messages: {condition: service_healthy}
      users: {condition: service_healthy}

  worker:
    <<: *fluxer-service
    image: ${FLUXER_REGISTRY:-ghcr.io/${FLUXER_REGISTRY_OWNER:-fluxerapp}}/fluxer-api:${FLUXER_IMAGE_TAG:-v1}
    deploy:
      replicas: ${FLUXER_WORKER_REPLICAS:-1}
      resources:
        limits:
          memory: ${FLUXER_WORKER_MEMORY_LIMIT:-2560mb}
        reservations:
          memory: ${FLUXER_WORKER_MEMORY_RESERVATION:-1gb}
    command: ["node", "dist/WorkerEntrypoint.js"]
    environment:
      <<: *fluxer-env
      NODE_OPTIONS: --max-old-space-size${FLUXER_WORKER_NODE_HEAP_MB:+=}${FLUXER_WORKER_NODE_HEAP_MB:--percentage=55}${FLUXER_WORKER_NODE_OPTIONS:+ ${FLUXER_WORKER_NODE_OPTIONS:-}}
      NODE_EXTRA_CA_CERTS: ${FLUXER_NODE_EXTRA_CA_CERTS:-/etc/ssl/certs/ca-certificates.crt}
      FLUXER_API_WORKER_MODE: all_lanes
      FLUXER_API_WORKER_ENABLE_CRON_SCHEDULER: "true"
      FLUXER_POSTGRES_MAX_CONNECTIONS: "${FLUXER_WORKER_POSTGRES_MAX_CONNECTIONS:-25}"
      FLUXER_WORKER_POSTGRES_SYNCHRONOUS_COMMIT: "${FLUXER_WORKER_POSTGRES_SYNCHRONOUS_COMMIT:-}"
    healthcheck:
      test: ["CMD-SHELL", "find /tmp/fluxer-worker-heartbeat -newermt '-30 seconds' | grep -q ."]
      <<: *fluxer-app-healthcheck
      retries: ${FLUXER_WORKER_HEALTHCHECK_RETRIES:-3}
    depends_on:
      postgres: {condition: service_healthy}
      valkey: {condition: service_healthy}
      nats: {condition: service_healthy}
      seaweedfs-init: {condition: service_completed_successfully}
      snowflakes: {condition: service_healthy}
      messages: {condition: service_healthy}
      users: {condition: service_healthy}

  gateway:
    <<: *fluxer-service
    image: ${FLUXER_REGISTRY:-ghcr.io/${FLUXER_REGISTRY_OWNER:-fluxerapp}}/fluxer-gateway:${FLUXER_IMAGE_TAG:-v1}
    ulimits:
      nofile:
        soft: 65536
        hard: 65536
    deploy:
      resources:
        limits:
          memory: ${FLUXER_GATEWAY_MEMORY_LIMIT:-1gb}
        reservations:
          memory: ${FLUXER_GATEWAY_MEMORY_RESERVATION:-384mb}
    environment:
      <<: *fluxer-env
      FLUXER_GATEWAY_PORT: "8080"
      FLUXER_GATEWAY_MEDIA_PROXY_ENDPOINT: ${FLUXER_GATEWAY_MEDIA_PROXY_ENDPOINT:-${FLUXER_MEDIA_ENDPOINT:-${FLUXER_PUBLIC_ORIGIN:-${FLUXER_PUBLIC_SCHEME:-https}://${FLUXER_DOMAIN:-}}/media}}
      FLUXER_GATEWAY_STATIC_CDN_ENDPOINT: ${FLUXER_GATEWAY_STATIC_CDN_ENDPOINT:-${FLUXER_STATIC_CDN_ENDPOINT:-${FLUXER_PUBLIC_ORIGIN:-${FLUXER_PUBLIC_SCHEME:-https}://${FLUXER_DOMAIN:-}}}}
      FLUXER_GATEWAY_LOGGER_LEVEL: ${FLUXER_GATEWAY_LOGGER_LEVEL:-}
      LOGGER_LEVEL: ${LOGGER_LEVEL:-}
      FLUXER_GATEWAY_PUSH_ENABLED: ${FLUXER_GATEWAY_PUSH_ENABLED:-}
      FLUXER_GATEWAY_PUSH_ENROLLED_CLEAR_NOTIFICATIONS_ENABLED: ${FLUXER_GATEWAY_PUSH_ENROLLED_CLEAR_NOTIFICATIONS_ENABLED:-}
      FLUXER_GATEWAY_PUSH_OUTBOX_REQUEST_TIMEOUT_MS: ${FLUXER_GATEWAY_PUSH_OUTBOX_REQUEST_TIMEOUT_MS:-}
      FLUXER_GATEWAY_PRESENCE_PUSH_BUFFER_MAX_ENTRIES: ${FLUXER_GATEWAY_PRESENCE_PUSH_BUFFER_MAX_ENTRIES:-}
      FLUXER_GATEWAY_PRESENCE_PUSH_BUFFER_MAX_BYTES: ${FLUXER_GATEWAY_PRESENCE_PUSH_BUFFER_MAX_BYTES:-}
      FLUXER_GATEWAY_HTTP_RPC_MAX_CONCURRENCY: ${FLUXER_GATEWAY_HTTP_RPC_MAX_CONCURRENCY:-}
      FLUXER_GATEWAY_NATS_RPC_MAX_HANDLERS: ${FLUXER_GATEWAY_NATS_RPC_MAX_HANDLERS:-}
      FLUXER_GATEWAY_ZSTD_WINDOW_LOG: ${FLUXER_GATEWAY_ZSTD_WINDOW_LOG:-}
      FLUXER_GATEWAY_HTTP_FAILURE_THRESHOLD: ${FLUXER_GATEWAY_HTTP_FAILURE_THRESHOLD:-}
      FLUXER_GATEWAY_HTTP_RECOVERY_TIMEOUT_MS: ${FLUXER_GATEWAY_HTTP_RECOVERY_TIMEOUT_MS:-}
      FLUXER_GATEWAY_GUILD_VISITOR_SESSIONS_MAX: ${FLUXER_GATEWAY_GUILD_VISITOR_SESSIONS_MAX:-}
      FLUXER_GATEWAY_GUILD_VISITOR_SESSIONS_FLOOR: ${FLUXER_GATEWAY_GUILD_VISITOR_SESSIONS_FLOOR:-}
      FLUXER_GATEWAY_GUILD_VISITOR_SESSIONS_PER_MEMBER: ${FLUXER_GATEWAY_GUILD_VISITOR_SESSIONS_PER_MEMBER:-}
      FLUXER_GATEWAY_GUILD_VISITOR_SESSIONS_PER_USER: ${FLUXER_GATEWAY_GUILD_VISITOR_SESSIONS_PER_USER:-}
      FLUXER_GATEWAY_GUILD_VISITOR_SESSIONS_PER_IP: ${FLUXER_GATEWAY_GUILD_VISITOR_SESSIONS_PER_IP:-}
      FLUXER_ERLANG_COOKIE: ${FLUXER_ERLANG_COOKIE:-}
      FLUXER_ERLANG_SCHEDULERS: ${FLUXER_ERLANG_SCHEDULERS:-}
      FLUXER_ERLANG_SCHEDULERS_MIN: ${FLUXER_ERLANG_SCHEDULERS_MIN:-}
      FLUXER_ERLANG_SCHEDULERS_MAX: ${FLUXER_ERLANG_SCHEDULERS_MAX:-}
      FLUXER_ERLANG_DIRTY_CPU_SCHEDULERS: ${FLUXER_ERLANG_DIRTY_CPU_SCHEDULERS:-}
      FLUXER_ERLANG_DIRTY_IO_SCHEDULERS: ${FLUXER_ERLANG_DIRTY_IO_SCHEDULERS:-}
    healthcheck:
      test: ["CMD-SHELL", "bash -c 'exec 3<>/dev/tcp/127.0.0.1/8080 && printf \"GET /_health/ready HTTP/1.0\\r\\nHost: 127.0.0.1\\r\\n\\r\\n\" >&3 && head -n 1 <&3 | grep -q \" 200 \"'"]
      <<: *fluxer-app-healthcheck
    depends_on:
      nats: {condition: service_healthy}
      valkey: {condition: service_healthy}

  media-proxy:
    <<: *fluxer-service
    image: ${FLUXER_REGISTRY:-ghcr.io/${FLUXER_REGISTRY_OWNER:-fluxerapp}}/fluxer-media-proxy:${FLUXER_IMAGE_TAG:-v1}
    deploy:
      resources:
        limits:
          memory: ${FLUXER_MEDIA_PROXY_MEMORY_LIMIT:-1gb}
    environment:
      <<: *fluxer-env
      FLUXER_MEDIA_PROXY_PORT: "8080"
      TOKIO_WORKER_THREADS: ${FLUXER_MEDIA_PROXY_TOKIO_WORKER_THREADS:-4}
      FLUXER_MEDIA_PROXY_MODE: upload
      FLUXER_MEDIA_PROXY_STORAGE_BACKEND: s3
      FLUXER_MEDIA_PROXY_CORS_MODE: ${FLUXER_MEDIA_PROXY_CORS_MODE:-}
      FLUXER_MEDIA_PROXY_CORS_ALLOWED_ORIGINS: ${FLUXER_MEDIA_PROXY_CORS_ALLOWED_ORIGINS:-${FLUXER_PUBLIC_ORIGIN:-${FLUXER_PUBLIC_SCHEME:-https}://${FLUXER_DOMAIN:-}:${FLUXER_PUBLIC_PORT:-443}}}
      FLUXER_MEDIA_PROXY_ATTACHMENT_SIGNATURE_MODE: ${FLUXER_MEDIA_PROXY_ATTACHMENT_SIGNATURE_MODE:-}
      FLUXER_MEDIA_PROXY_READ_ONLY: ${FLUXER_MEDIA_PROXY_READ_ONLY:-}
      FLUXER_MEDIA_PROXY_NSFW_THRESHOLD: ${FLUXER_MEDIA_PROXY_NSFW_THRESHOLD:-}
      FLUXER_NSFW_SERVICE_ENDPOINT: ${FLUXER_NSFW_SERVICE_ENDPOINT:-}
      FLUXER_MEDIA_PROXY_MAX_NATIVE_TRANSFORMS: ${FLUXER_MEDIA_PROXY_MAX_NATIVE_TRANSFORMS:-}
      FLUXER_MEDIA_PROXY_WORKER_QUEUE_CAPACITY: ${FLUXER_MEDIA_PROXY_WORKER_QUEUE_CAPACITY:-}
      FLUXER_MEDIA_PROXY_MAX_ENCODE_DURATION_MS: ${FLUXER_MEDIA_PROXY_MAX_ENCODE_DURATION_MS:-}
      FLUXER_MEDIA_PROXY_MAX_ENCODE_FRAMES: ${FLUXER_MEDIA_PROXY_MAX_ENCODE_FRAMES:-}
      FLUXER_MEDIA_PROXY_TRANSFORM_TIMEOUT_MS: ${FLUXER_MEDIA_PROXY_TRANSFORM_TIMEOUT_MS:-}
      FLUXER_MEDIA_PROXY_TRANSFORM_CACHE_BYTES: ${FLUXER_MEDIA_PROXY_TRANSFORM_CACHE_BYTES:-}
      FLUXER_MEDIA_PROXY_TRANSFORM_CACHE_MAX_ENTRY_BYTES: ${FLUXER_MEDIA_PROXY_TRANSFORM_CACHE_MAX_ENTRY_BYTES:-}
      FLUXER_MEDIA_PROXY_TRANSFORM_CACHE_TTL_MS: ${FLUXER_MEDIA_PROXY_TRANSFORM_CACHE_TTL_MS:-}
      FLUXER_MEDIA_PROXY_SOCKET_IO_TIMEOUT_MS: ${FLUXER_MEDIA_PROXY_SOCKET_IO_TIMEOUT_MS:-}
      FLUXER_MEDIA_PROXY_SHUTDOWN_GRACE_MS: ${FLUXER_MEDIA_PROXY_SHUTDOWN_GRACE_MS:-}
      FLUXER_MEDIA_PROXY_UPLOAD_RELAY_S3_TIMEOUT_MS: ${FLUXER_MEDIA_PROXY_UPLOAD_RELAY_S3_TIMEOUT_MS:-}
      FLUXER_MEDIA_PROXY_UPLOAD_RELAY_BUFFERED_RETRY_BYTES: ${FLUXER_MEDIA_PROXY_UPLOAD_RELAY_BUFFERED_RETRY_BYTES:-}
      FLUXER_MEDIA_PROXY_UPLOAD_RELAY_BUFFERED_RETRY_TOTAL_BYTES: ${FLUXER_MEDIA_PROXY_UPLOAD_RELAY_BUFFERED_RETRY_TOTAL_BYTES:-}
      FLUXER_MEDIA_PROXY_UPLOAD_RELAY_SPOOL_DIR: ${FLUXER_MEDIA_PROXY_UPLOAD_RELAY_SPOOL_DIR:-}
      FLUXER_MEDIA_PROXY_UPLOAD_RELAY_SPOOL_CHUNK_BYTES: ${FLUXER_MEDIA_PROXY_UPLOAD_RELAY_SPOOL_CHUNK_BYTES:-}
      FLUXER_MEDIA_PROXY_UPLOAD_RELAY_SPOOL_MAX_TOTAL_BYTES: ${FLUXER_MEDIA_PROXY_UPLOAD_RELAY_SPOOL_MAX_TOTAL_BYTES:-2147483648}
      FLUXER_S3_SESSION_TOKEN: ${FLUXER_S3_SESSION_TOKEN:-}
      FLUXER_S3_READ_ENDPOINT: ${FLUXER_S3_READ_ENDPOINT:-}
      FLUXER_S3_READ_BUCKET: ${FLUXER_S3_READ_BUCKET:-}
      FLUXER_S3_READ_BUCKET_STYLE: ${FLUXER_S3_READ_BUCKET_STYLE:-}
      FLUXER_S3_READ_SIGNED: "${FLUXER_S3_READ_SIGNED:-true}"
    volumes:
      - media-proxy-tmp:/tmp
    healthcheck:
      test: ["CMD", "/usr/local/bin/fluxer-media-proxy", "healthcheck"]
      <<: *fluxer-healthcheck
      interval: ${FLUXER_MEDIA_PROXY_HEALTHCHECK_INTERVAL:-30s}
    depends_on:
      seaweedfs-init: {condition: service_completed_successfully}
      nats: {condition: service_healthy}

  push:
    <<: *fluxer-service
    image: ${FLUXER_REGISTRY:-ghcr.io/${FLUXER_REGISTRY_OWNER:-fluxerapp}}/fluxer-push:${FLUXER_IMAGE_TAG:-v1}
    deploy:
      resources:
        limits:
          memory: ${FLUXER_PUSH_MEMORY_LIMIT:-256mb}
    environment:
      <<: *fluxer-rust-env
      FLUXER_PUSH_SERVICE_QUEUE_CAPACITY: ${FLUXER_PUSH_SERVICE_QUEUE_CAPACITY:-}
      FLUXER_PUSH_SERVICE_SEND_CONCURRENCY: ${FLUXER_PUSH_SERVICE_SEND_CONCURRENCY:-}
      FLUXER_PUSH_SERVICE_APNS_BASE_URL: ${FLUXER_PUSH_SERVICE_APNS_BASE_URL:-}
      FLUXER_PUSH_SERVICE_FCM_BASE_URL: ${FLUXER_PUSH_SERVICE_FCM_BASE_URL:-}
      FLUXER_PUSH_SERVICE_MANAGED_RELAY_HOSTS: ${FLUXER_PUSH_SERVICE_MANAGED_RELAY_HOSTS:-}
      FLUXER_PUSH_SERVICE_OWN_RELAY_HOSTS: ${FLUXER_PUSH_SERVICE_OWN_RELAY_HOSTS:-}
      FLUXER_PUSH_SERVICE_PRIVATE_HOSTS: ${FLUXER_PUSH_SERVICE_PRIVATE_HOSTS:-}
      FLUXER_PUSH_SERVICE_RELAY_CONSENT_ACCEPTED: ${FLUXER_PUSH_SERVICE_RELAY_CONSENT_ACCEPTED:-}
      FLUXER_PUSH_APNS_DEFAULT_ENVIRONMENT: ${FLUXER_PUSH_APNS_DEFAULT_ENVIRONMENT:-}
      FLUXER_PUSH_FCM_ENABLED: ${FLUXER_PUSH_FCM_ENABLED:-}
      FLUXER_PUSH_FCM_PROJECT_ID: ${FLUXER_PUSH_FCM_PROJECT_ID:-}
      FLUXER_PUSH_FCM_CLIENT_EMAIL: ${FLUXER_PUSH_FCM_CLIENT_EMAIL:-}
      FLUXER_PUSH_FCM_PRIVATE_KEY: ${FLUXER_PUSH_FCM_PRIVATE_KEY:-}
      FLUXER_PUSH_FCM_PRIVATE_KEY_PATH: ${FLUXER_PUSH_FCM_PRIVATE_KEY_PATH:-}
      FLUXER_PUSH_FCM_SERVICE_ACCOUNT_JSON_PATH: ${FLUXER_PUSH_FCM_SERVICE_ACCOUNT_JSON_PATH:-}
      FLUXER_PUSH_FCM_TOKEN_URI: ${FLUXER_PUSH_FCM_TOKEN_URI:-}
      FLUXER_PUSH_FCM_APPS: ${FLUXER_PUSH_FCM_APPS:-}
    healthcheck:
      test: ["CMD", "/usr/local/bin/fluxer-push", "healthcheck"]
      <<: *fluxer-app-healthcheck
      start_period: ${FLUXER_SVC_HEALTHCHECK_START_PERIOD:-60s}
    depends_on:
      nats: {condition: service_healthy}
      api: {condition: service_healthy}

  static-proxy:
    <<: *fluxer-service
    image: ${FLUXER_REGISTRY:-ghcr.io/${FLUXER_REGISTRY_OWNER:-fluxerapp}}/fluxer-static:${FLUXER_IMAGE_TAG:-v1}
    deploy:
      resources:
        limits:
          memory: ${FLUXER_STATIC_PROXY_MEMORY_LIMIT:-256mb}
    healthcheck:
      test: ["CMD", "wget", "-q", "-O", "/dev/null", "http://127.0.0.1:8080/avatars/0.png"]
      <<: *fluxer-healthcheck

  app-proxy:
    <<: *fluxer-service
    image: ${FLUXER_REGISTRY:-ghcr.io/${FLUXER_REGISTRY_OWNER:-fluxerapp}}/fluxer-app-proxy-self-hosted:${FLUXER_IMAGE_TAG:-v1}
    deploy:
      resources:
        limits:
          memory: ${FLUXER_APP_PROXY_MEMORY_LIMIT:-256mb}
    environment:
      RUST_LOG: ${RUST_LOG:-}
      TOKIO_WORKER_THREADS: ${FLUXER_TOKIO_WORKER_THREADS:-2}
      FLUXER_APP_PROXY_PORT: "8080"
      FLUXER_S3_PUBLIC_ENDPOINT: ${FLUXER_S3_PUBLIC_ENDPOINT:-}
      FLUXER_S3_BUCKET_UPLOADS: ${FLUXER_S3_BUCKET_UPLOADS:-}
      FLUXER_STATIC_CDN_ENDPOINT: ${FLUXER_STATIC_CDN_ENDPOINT:-}
      FLUXER_MEDIA_ENDPOINT: ${FLUXER_MEDIA_ENDPOINT:-}
      FLUXER_APP_PRODUCT_NAME: ${FLUXER_APP_PRODUCT_NAME:-}
      FLUXER_APP_ICON_URL: ${FLUXER_APP_ICON_URL:-}
      FLUXER_APP_SYMBOL_URL: ${FLUXER_APP_SYMBOL_URL:-}
      FLUXER_APP_LOGO_URL: ${FLUXER_APP_LOGO_URL:-}
      FLUXER_APP_WORDMARK_URL: ${FLUXER_APP_WORDMARK_URL:-}
      FLUXER_APP_FAVICON_URL: ${FLUXER_APP_FAVICON_URL:-}
      FLUXER_APP_FAVICON_DARK_URL: ${FLUXER_APP_FAVICON_DARK_URL:-}
      FLUXER_APP_SIGN_IN_BACKDROP_URL: ${FLUXER_APP_SIGN_IN_BACKDROP_URL:-}
      FLUXER_APP_THEME_COLOR: ${FLUXER_APP_THEME_COLOR:-}
      DISCOVERY_UPSTREAM_URL: http://edge:8088/.well-known/fluxer
      DISCOVERY_REFRESH_INTERVAL_MS: ${DISCOVERY_REFRESH_INTERVAL_MS:-5000}
      FLUXER_APP_PROXY_INDEX_UPSTREAM_URL: ${FLUXER_APP_PROXY_INDEX_UPSTREAM_URL:-}
      FLUXER_APP_PROXY_SAME_ORIGIN_HOSTS: ${FLUXER_APP_PROXY_SAME_ORIGIN_HOSTS:-}
      FLUXER_APP_PROXY_MANIFEST_SCOPE_EXTENSIONS: ${FLUXER_APP_PROXY_MANIFEST_SCOPE_EXTENSIONS:-}
      FLUXER_LIVEKIT_URL: ${FLUXER_LIVEKIT_URL:-}
      FLUXER_CSP_EXTRA_DEFAULT_SRC: ${FLUXER_CSP_EXTRA_DEFAULT_SRC:-}
      FLUXER_CSP_EXTRA_CONNECT_SRC: ${FLUXER_CSP_EXTRA_CONNECT_SRC:-}
      FLUXER_CSP_EXTRA_IMG_SRC: ${FLUXER_CSP_EXTRA_IMG_SRC:-}
      FLUXER_CSP_EXTRA_MEDIA_SRC: ${FLUXER_CSP_EXTRA_MEDIA_SRC:-}
      FLUXER_CSP_EXTRA_FONT_SRC: ${FLUXER_CSP_EXTRA_FONT_SRC:-}
      FLUXER_CSP_EXTRA_SCRIPT_SRC: ${FLUXER_CSP_EXTRA_SCRIPT_SRC:-}
      FLUXER_CSP_EXTRA_STYLE_SRC: ${FLUXER_CSP_EXTRA_STYLE_SRC:-}
      FLUXER_CSP_EXTRA_FRAME_SRC: ${FLUXER_CSP_EXTRA_FRAME_SRC:-}
      FLUXER_CSP_EXTRA_WORKER_SRC: ${FLUXER_CSP_EXTRA_WORKER_SRC:-}
      FLUXER_CSP_EXTRA_MANIFEST_SRC: ${FLUXER_CSP_EXTRA_MANIFEST_SRC:-}
      FLUXER_CSP_REPORT_URI: ${FLUXER_CSP_REPORT_URI:-}
    healthcheck:
      test: ["CMD", "curl", "-fsS", "-o", "/dev/null", "http://127.0.0.1:8080/_ready"]
      <<: *fluxer-healthcheck
      interval: ${FLUXER_APP_PROXY_HEALTHCHECK_INTERVAL:-30s}
    depends_on:
      api: {condition: service_healthy}
      edge: {condition: service_healthy}

  snowflakes:
    <<: *fluxer-service
    stop_grace_period: 30s
    image: ${FLUXER_REGISTRY:-ghcr.io/${FLUXER_REGISTRY_OWNER:-fluxerapp}}/fluxer-snowflakes:${FLUXER_IMAGE_TAG:-v1}
    deploy:
      resources:
        limits:
          memory: ${FLUXER_SNOWFLAKES_MEMORY_LIMIT:-256mb}
    environment:
      <<: *fluxer-rust-env
      FLUXER_SVC_NAME: snowflakes
      FLUXER_SVC_MODE: combined
    healthcheck: *fluxer-svc-healthcheck
    depends_on:
      nats: {condition: service_healthy}

  users:
    <<: *fluxer-service
    stop_grace_period: 30s
    image: ${FLUXER_REGISTRY:-ghcr.io/${FLUXER_REGISTRY_OWNER:-fluxerapp}}/fluxer-users:${FLUXER_IMAGE_TAG:-v1}
    deploy:
      resources:
        limits:
          memory: ${FLUXER_USERS_MEMORY_LIMIT:-256mb}
    environment:
      <<: *fluxer-rust-env
      FLUXER_SVC_NAME: users
      FLUXER_SVC_MODE: combined
      FLUXER_POSTGRES_MAX_CONNECTIONS: "${FLUXER_USERS_POSTGRES_MAX_CONNECTIONS:-20}"
    healthcheck: *fluxer-svc-healthcheck
    depends_on:
      nats: {condition: service_healthy}
      postgres: {condition: service_healthy}

  gifs:
    <<: *fluxer-service
    stop_grace_period: 30s
    image: ${FLUXER_REGISTRY:-ghcr.io/${FLUXER_REGISTRY_OWNER:-fluxerapp}}/fluxer-gifs:${FLUXER_IMAGE_TAG:-v1}
    deploy:
      resources:
        limits:
          memory: ${FLUXER_GIFS_MEMORY_LIMIT:-256mb}
    environment:
      <<: *fluxer-rust-env
      FLUXER_SVC_NAME: gifs
      FLUXER_SVC_MODE: combined
      FLUXER_GIFS_SHARD_CACHE_MAX_BYTES: ${FLUXER_GIFS_SHARD_CACHE_MAX_BYTES:-134217728}
    healthcheck: *fluxer-svc-healthcheck
    depends_on:
      nats: {condition: service_healthy}

  messages:
    <<: *fluxer-service
    stop_grace_period: 30s
    image: ${FLUXER_REGISTRY:-ghcr.io/${FLUXER_REGISTRY_OWNER:-fluxerapp}}/fluxer-messages:${FLUXER_IMAGE_TAG:-v1}
    deploy:
      resources:
        limits:
          memory: ${FLUXER_MESSAGES_MEMORY_LIMIT:-256mb}
    environment:
      <<: *fluxer-rust-env
      FLUXER_SVC_NAME: messages
      FLUXER_SVC_MODE: combined
      FLUXER_POSTGRES_MAX_CONNECTIONS: "${FLUXER_MESSAGES_POSTGRES_MAX_CONNECTIONS:-20}"
    healthcheck: *fluxer-svc-healthcheck
    depends_on:
      nats: {condition: service_healthy}
      postgres: {condition: service_healthy}

  unfurl:
    <<: *fluxer-service
    stop_grace_period: 30s
    image: ${FLUXER_REGISTRY:-ghcr.io/${FLUXER_REGISTRY_OWNER:-fluxerapp}}/fluxer-unfurl:${FLUXER_IMAGE_TAG:-v1}
    deploy:
      resources:
        limits:
          memory: ${FLUXER_UNFURL_MEMORY_LIMIT:-256mb}
    environment:
      <<: *fluxer-rust-env
      FLUXER_SVC_NAME: unfurl
      FLUXER_SVC_MODE: combined
      FLUXER_MEDIA_PROXY_ENDPOINT: http://media-proxy:8080
      FLUXER_ACTIVITYPUB_SIGNING_KEY_BASE64: ${FLUXER_ACTIVITYPUB_SIGNING_KEY_BASE64:-}
    healthcheck: *fluxer-svc-healthcheck
    depends_on:
      nats: {condition: service_healthy}

  admin:
    <<: *fluxer-service
    image: ${FLUXER_REGISTRY:-ghcr.io/${FLUXER_REGISTRY_OWNER:-fluxerapp}}/fluxer-admin:${FLUXER_IMAGE_TAG:-v1}
    deploy:
      resources:
        limits:
          memory: ${FLUXER_ADMIN_MEMORY_LIMIT:-256mb}
    environment:
      <<: *fluxer-rust-env
      FLUXER_ADMIN_PORT: "8080"
      FLUXER_ADMIN_BASE_PATH: ${FLUXER_ADMIN_BASE_PATH:-/admin}
      FLUXER_API_ENDPOINT: http://api:8080
      FLUXER_API_PUBLIC_ENDPOINT: ${FLUXER_API_ENDPOINT:-}
      FLUXER_STATIC_CDN_ENDPOINT: ${FLUXER_STATIC_CDN_ENDPOINT:-${FLUXER_PUBLIC_ORIGIN:-${FLUXER_PUBLIC_SCHEME:-https}://${FLUXER_DOMAIN:-}}}
    healthcheck:
      test: ["CMD-SHELL", "bash -c 'exec 3<>/dev/tcp/127.0.0.1/8080 && printf \"GET /_health HTTP/1.0\\r\\n\\r\\n\" >&3 && head -n 1 <&3 | grep -q \" 200 \"'"]
      <<: *fluxer-app-healthcheck
      start_period: ${FLUXER_SVC_HEALTHCHECK_START_PERIOD:-60s}
    depends_on:
      api: {condition: service_healthy}

networks:
  fluxer:
    driver: bridge

volumes:
  edge-data:
  edge-config:
  postgres-data:
  valkey-data:
  nats-data:
  meilisearch-data:
  seaweedfs-data:
  media-proxy-tmp:

This file may be out of date. Check the Fluxer 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 Oct 27, 2026

Alternatives to Fluxer

Zulip logo

Zulip

26.2k
Zulip is an open-source team chat app with topic-based threading for organized live and asynchronous conversations.
Custom CommunicationsPermissive LicenseSelf Hosting Solutions
Details
Rocket.Chat logo

Rocket.Chat

46.4k
An open-source, secure, fully customizable communications platform for team collaboration and messaging.
Custom CommunicationsFOSS with IssuesOpen-Washing Risk+3
14 ContainersFOSS with Issues
Details
Tailchat logo

Tailchat

3.6k
Tailchat is a self-hostable next-generation noIM application platform for team collaboration in an own workspace, with a plugin system and microservice architecture.
Custom CommunicationsSelf Hosting SolutionsPermissive License
Details
Tinode logo

Tinode

13.6k
Tinode is a full-stack instant messaging server with native clients for web, Android, iOS, and command-line use.
Custom CommunicationsVideo ConferencingSelf Hosting Solutions+1
Details
chatto logo

chatto

2.9k
Chatto is a self-hostable chat application for teams and communities, with a bundled web frontend, server, CLI, and related identity/workflow components.
Custom CommunicationsVideo ConferencingSelf Hosting Solutions+1
4 Containers
Details
RetroShare logo

RetroShare

2.0k
RetroShare is a decentralized, private, secure, cross-platform communication toolkit for sharing files, chat, messages, forums, and channels.
Custom CommunicationsCopyleft LicenseSocial Networks+2
Details
Mumble logo

Mumble

8.4k
Mumble is an open source, low-latency, high-quality voice chat application with separate client and server components.
Custom CommunicationsAudio StreamingSelf Hosting Solutions+1
Details
once-campfire logo

once-campfire

4.8k
Campfire is a web-based chat application for self-hosted team messaging with rooms, direct messages, file attachments, search, and notifications.
Custom CommunicationsSelf Hosting SolutionsGroupware Suites
Details
SimpleX Chat logo

SimpleX Chat

19.7k
A private messaging platform with no user identifiers, offering encrypted chat, groups, and calls across mobile and terminal apps.
Custom CommunicationsVideo ConferencingSelf Hosting Solutions+1
Cloud Required
Details
Databag logo

Databag

1.5k
Databag is a federated messenger for self-hosting.
Custom CommunicationsVideo ConferencingSelf Hosting Solutions+2
Single Container
Details
Salut à Toi logo

Salut à Toi

Multipurpose, multi frontend, libre and decentralized communication tool.
XMPP Web ClientsCopyleft LicenseSocial Networks+2
Details

Related Software

Openfire logo

Openfire

3.1k
Openfire is an open-source XMPP server for real-time collaboration and instant messaging.
XMPP ServersCustom CommunicationsSelf Hosting Solutions+1
Details
MongooseIM logo

MongooseIM

1.8k
MongooseIM is a scalable XMPP server platform for building instant messaging systems.
XMPP ServersCustom CommunicationsSelf Hosting Solutions+1
Details
Gotify logo

Gotify

16.2k
Gotify/server is a self-hosted message server for sending and receiving real-time notifications.
Custom CommunicationsPermissive LicenseSelf Hosting Solutions+1
Single Container
Details
Misskey logo

Misskey

11.4k
Misskey is an open-source federated social media platform.
Social NetworksSocial MediaSelf Hosting Solutions+2
2 Containers
Details
Jitsi Meet logo

Jitsi Meet

30.2k
Jitsi Meet is an open source video conferencing platform for browser-based meetings with mobile apps and SDKs.
Video ConferencingPermissive LicenseSelf Hosting Solutions
Single Container
Details
Element logo

Element

13.6k
Element is a Matrix web and desktop client.
Custom CommunicationsVideo ConferencingPermissive License
Open Core
Details
Discourse logo

Discourse

48.1k
An open-source community platform for running discussion forums and real-time chat.
Social NetworksCopyleft LicenseSelf Hosting Solutions+1
Details
Mattermost logo

Mattermost

39.6k
Mattermost is a self-hosted collaboration platform for chat, workflow automation, voice calling, screen sharing, and AI integration.
Custom CommunicationsFOSS with IssuesOpen-Washing Risk+4
15 ContainersOpen CoreFOSS with Issues
Details
Hyphanet logo

Hyphanet

1.2k
Freenet is a peer-to-peer platform for censorship-resistant communication and publishing, providing a distributed, encrypted, decentralized datastore.
Custom CommunicationsCopyleft LicenseSocial Networks+2
Details
Asterisk logo

Asterisk

3.7k
An open source PBX and telephony toolkit for building and running voice communication systems.
SIP CommunicationCustom CommunicationsSelf Hosting Solutions+1
Details