Self Hosting

Configuration

For the most up to date view on all configuration options, please refer to the source code.

For self hosting, the following options are recommended:

# This is needed for webhooks to correctly reach the backend
PUBLIC_API_URL: <your-public-url>

# Depending on your deployment environment, you can choose to run the 
# the user code in a container on the local machine or on a kubernetes cluster
RUNTIME_TYPE: docker | kubernetes

# Saves you the hassle of running migrations seperately
RUN_MIGRATIONS_ON_STARTUP: true

Components & Services

  • Floww Backend
  • Floww Dashboard
  • Centrifugo
  • Registry

Deployment options

Docker Compose

The simplest way to get started with self hosting is to use docker compose

When using docker compose for self hosting floww will spin up images with the runtimes for user code and will scale the container back to 0 when not in use (after 5 minutes of inactivity).

Requirements

  • Docker
  • Docker Compose

Deployment

Create the following files

.env
PUBLIC_API_URL: <your-public-url> # TODO: add your public url
RUNTIME_TYPE: docker
RUN_MIGRATIONS_ON_STARTUP: true
docker-compose.yml
services:
  backend:
    image: ghcr.io/usefloww/floww-backend:latest
    container_name: "floww-backend"
    env_file:
      - .env
    environment:
      - REGISTRY_URL=http://registry:5000
      - REGISTRY_AUTH_USER=${DOCKER_REGISTRY_USER:-}
      - REGISTRY_AUTH_PASSWORD=${DOCKER_REGISTRY_PASSWORD:-}
      - REGISTRY_REPOSITORY_NAME=floww-runtime
      - RUNTIME_TYPE=docker
      - AUTH_TYPE=password
      - RUN_MIGRATIONS_ON_STARTUP=true
      - SINGLE_ORG_MODE="true"                          # Enable single-org mode
      - SINGLE_ORG_NAME="default"                        # Organization slug/identifier
      - SINGLE_ORG_DISPLAY_NAME="My Organization"      # Display name
      - SINGLE_ORG_DEFAULT_ROLE="owner"                  # Default role for users (owner/admin/member)
      - SINGLE_ORG_ALLOW_PERSONAL_NAMESPACES="false"     # Allow personal workflow namespaces
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock"
      - "~/.docker/config.json:/root/.docker/config.json"
    user: root
    networks:
      - floww-network
    depends_on:
      db:
        condition: service_healthy
      registry:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "python3", "-c", "import requests; r=requests.get('http://localhost:8000/api/health', timeout=5); r.raise_for_status()"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s
    restart: unless-stopped

  dashboard:
    image: ghcr.io/usefloww/floww-dashboard:latest
    container_name: "floww-dashboard"
    environment:
      - BACKEND_URL=http://backend:8000
    ports:
      - "3000:3000"
    env_file:
      - .env
    networks:
      - floww-network
    depends_on:
      backend:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 20s
    restart: unless-stopped

  db:
    container_name: floww-db
    image: postgres:18-alpine3.22
    environment:
      - POSTGRES_DB=postgres
      - POSTGRES_USER=admin
      - POSTGRES_PASSWORD=secret
    volumes:
      - postgres-data:/var/lib/postgresql/data
    networks:
      - floww-network
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U admin -d postgres"]
      interval: 5s
      timeout: 5s
      retries: 5
    restart: always


  centrifugo:
    container_name: floww-centrifugo
    image: ghcr.io/usefloww/floww-centrifugo:latest
    environment:
      - CENTRIFUGO_API_KEY=${CENTRIFUGO_API_KEY:-floww-api-key-dev}
      - CENTRIFUGO_ADMIN_ENABLED=${CENTRIFUGO_ADMIN_ENABLED:-false}
      - CENTRIFUGO_ADMIN_INSECURE=${CENTRIFUGO_ADMIN_INSECURE:-false}
      - CENTRIFUGO_CONNECT_PROXY_ENDPOINT=${CENTRIFUGO_CONNECT_PROXY_ENDPOINT:-http://backend:8000/centrifugo/connect}
      - CENTRIFUGO_SUBSCRIBE_PROXY_ENDPOINT=${CENTRIFUGO_SUBSCRIBE_PROXY_ENDPOINT:-http://backend:8000/centrifugo/subscribe}
      - CENTRIFUGO_ALLOWED_ORIGINS=${CENTRIFUGO_ALLOWED_ORIGINS:-["https://dashboard.localhost", "https://api.localhost"]}
      - CENTRIFUGO_ALLOW_ANONYMOUS=${CENTRIFUGO_ALLOW_ANONYMOUS:-false}
    networks:
      - floww-network
    depends_on:
      backend:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8000/health"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 10s
    ulimits:
      nofile:
        soft: 65535
        hard: 65535
    restart: always

  registry:
    container_name: floww-registry
    image: registry:2.8.3
    ports:
      - "5001:5000"
    environment:
      - REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY=/var/lib/registry
    volumes:
      - registry-data:/var/lib/registry
    networks:
      - floww-network
    healthcheck:
      test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:5000/v2/"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 10s
    restart: always

networks:
  floww-network:
    driver: bridge

volumes:
  postgres-data:
  registry-data:

Now you should be able to start the services with:

docker compose up -d

Kubernetes

Coming soon