Skip to content

Architecture

This page describes every moving part of a Falak installation and how they communicate. Read it when you plan a production setup, debug connectivity, or review security.

Component Where it runs What it does
Control plane One host, as a Docker Compose stack in /opt/falak Laravel 12 app with the UI (Inertia + React), the REST API, the agent API, the build queue, alerting and Insights. Stores state in PostgreSQL 17 and Valkey.
Edge On the control plane host (edge service, Caddy) Terminates TLS for the panel (Let’s Encrypt) and for the agent API (Falak’s own CA, mutual TLS).
Builder On the control plane host (builder service), and optionally on builder servers falak-builder serve long-polls the control plane for build jobs, clones your repository, builds, and uploads an artifact or pushes an image.
Agent On every managed server (falak-agent, systemd) Enrolls with a one-time token, then long-polls for commands, runs them, streams output, sends heartbeats every 15 s, supervises processes and cron, and relays telemetry.
Caddy / FrankenPHP On servers that serve HTTP Serves your sites. FrankenPHP embeds Caddy and runs PHP; PHP-FPM servers and load balancers get a standalone Caddy. The agent configures it through the Caddy admin API on 127.0.0.1:2019.
Observability stack (optional) On the control plane host with --observability Loki (logs), Tempo (traces), VictoriaMetrics (metrics), Grafana (dashboards) and an OTLP gateway.
falak CLI Your computer or CI Talks to the public REST API with a bearer token.

The Compose project falak contains these services:

Service Role
control-plane The panel and REST API (FrankenPHP in worker mode)
agent-api Agent endpoints (/agent/*), installer (/install/*) and builder API (/api/internal/*), in classic PHP mode with its own thread pool
horizon Queue workers (deployments, provisioning, alert delivery, …)
reverb WebSockets for live updates in the UI
scheduler Runs schedule:run every minute
postgres PostgreSQL 17
valkey Cache, queues, sessions, agent wake-ups
edge Caddy with Let’s Encrypt and mTLS
builder falak-builder serve for native builds
gateway, loki, tempo, victoriametrics, grafana Only with the observability profile
Who talks to whom
browser ──HTTPS 443──► edge ──► control-plane (UI, API) wss://<panel>/app/… ──► reverb
falak CLI / CI ──HTTPS──► edge ──► control-plane (/api/v1)
GitHub/GitLab/Bitbucket ──HTTPS webhooks──► edge ──► control-plane (/api/webhooks/…)
falak-agent ──HTTPS + client cert──► agents.<panel> ──► agent-api (/agent/v1) (outbound from servers)
falak-agent ──HTTPS──► <panel>/install/agent/… (agent binary download, upgrades)
falak-agent ──HTTPS──► <panel>/api/internal/artifacts/… or S3 (release artifacts)
falak-agent ──OTLP/HTTP──► <panel>/otlp ──► gateway ──► Loki · Tempo · VictoriaMetrics
builder ──HTTPS──► <panel>/api/internal/builds/next (long-poll for jobs)
builder ──git clone──► your git provider

Servers never need inbound connections from the control plane. The agent dials out; SSH stays available as your own fallback.

  • Transport: HTTPS with mutual TLS. The control plane runs an internal certificate authority, the Fleet CA. Agent certificates are valid for 90 days and renew automatically; the CA is valid for 10 years.
  • Enrollment: the install command downloads the agent, then POST /agent/v1/enroll with the one-time token, a CSR and host facts. The agent receives a signed certificate and an agent id.
  • Commands: the agent long-polls GET /agent/v1/commands?wait=30. Commands are idempotent and carry a JSON-Schema-validated payload. State-style commands (*.apply) send the full desired state, and the agent converges to it.
  • Results: POST /agent/v1/commands/{id}/events with batched NDJSON events (started, output, progress, finished).
  • Heartbeat: POST /agent/v1/heartbeat every 15 s with facts and a metrics summary. An agent is offline after 60 s of silence.
  • Restarts: each agent process sends a session id. Commands delivered to a previous process are re-delivered when they are safe to repeat (Caddy routes, processes, cron, firewall, telemetry), otherwise failed with “The agent restarted before running the command”. A command never acknowledged is handled the same way after a 90 s lease.

Command namespaces: system, provision, runtime, edge, deploy, proc, cron, db, net, docker, telemetry, terminal.

One deployment, three servers
control plane ──job──► builder: clone, install, build ──artifact──► storage
control plane ──deploy.fetch──► every server (download + unpack release)
control plane ──deploy.prepare──► every server (.env, shared paths, permissions)
control plane ──deploy hook (migrate)──► leader only
control plane ──deploy.activate──► every server (switch current symlink, reload)
control plane ──proc.apply──► every server (restart workers, Octane, …)
control plane ──HTTP health check──► every server

Details: Deployments and releases.

Telemetry does not pass through the control plane database:

your app ──(falak/apm-laravel | @falak/apm-node)──► unix:/run/falak/otlp.sock or 127.0.0.1:4318
host metrics, log files, container logs ─────────► falak-agent (batch, retry, disk buffer)
falak-agent ──OTLP/HTTP──► Loki · Tempo · VictoriaMetrics ──► Grafana and the Falak UI
falak-agent ──exceptions, threshold breaches, cron heartbeats──► control plane (Insights)
Location Contents
/opt/falak (control plane host) .env, custom.env, deploy/, observability/, backups/
/srv/falak/sites/<site> (servers) releases/, shared/, current symlink
/etc/falak (servers) Agent key and certificate, CA certificate, agent config
/var/log/falak (servers) Supervised program logs, access/<site>.log edge access logs

The full list is in Ports and file paths.