Skip to content

Install Falak (production)

Falak runs as a Docker Compose stack on a single Linux host. The installer sets up Docker, writes a generated configuration to /opt/falak/.env, starts the stack and creates the first administrator. Day-2 operations use falak-ctl.

What you are installing
internet ──:80/:443──► edge (Caddy)
├─ falak.example.com Let's Encrypt ─► control-plane (FrankenPHP) · reverb (websockets)
├─ agents.falak.example.com Fleet-CA cert + client-cert (mTLS) check ─► agent-api
├─ registry.falak.example.com Let's Encrypt + basic auth ─► registry (built-in image registry)
└─ grafana.falak.example.com Let's Encrypt ─► grafana (optional)
control-plane · agent-api · horizon · reverb · scheduler ─► postgres 17 · valkey
builder (falak-builder serve: PHP/Composer, Node, Bun) ─► edge
  • A host that meets the requirements: Ubuntu 22.04/24.04 or Debian 12, amd64 or arm64, 4 GB RAM (8 GB with observability), ports 80 and 443 free.
  • DNS records for falak.example.com, agents.falak.example.com and registry.falak.example.com (plus grafana. with observability) pointing at the host.
Terminal window
curl -fsSL https://falak.sh/install.sh \
| sudo bash -s -- --domain falak.example.com --email you@example.com

Pin a release by using that release’s copy of the script:

Terminal window
curl -fsSL https://github.com/OthmanHaba/falak/releases/download/v0.2.6/install.sh \
| sudo bash -s -- --domain falak.example.com --email you@example.com
  1. Preflight: checks root, OS, CPU architecture, RAM, disk, that ports 80/443 are free, and that DNS for the panel and agents. (and grafana. with --observability) points at this host’s public IP. It prints missing records.
  2. Installs Docker Engine and the Compose plugin from Docker’s official apt repository if missing.
  3. Downloads falak-deploy.tar.gz, verifies it against SHA256SUMS, unpacks it to /opt/falak/{deploy,observability}, and installs falak-ctl to /usr/local/bin.
  4. Generates /opt/falak/.env (mode 600): APP_KEY, database and Valkey passwords, Reverb keys, the builder token, the OTLP token, the registry credentials and all FALAK_* URLs.
  5. Pulls ghcr.io/<owner>/falak-{control-plane,builder,edge}:<version>, starts the stack and waits until every service is healthy (up to 10 minutes). Migrations run in the control-plane service on start.
  6. With --observability, creates a Grafana service account token for Falak.
  7. Creates the first administrator and prints the password once.

Every option can also be set with the environment variable in the second column.

Option Env Default Meaning
--domain NAME FALAK_DOMAIN required Panel domain; agents use agents.NAME
--email ADDRESS FALAK_EMAIL required Let’s Encrypt account and first admin e-mail
--admin-email ADDRESS FALAK_ADMIN_EMAIL --email First admin, if different
--version TAG FALAK_VERSION latest release Release to install
--observability FALAK_OBSERVABILITY=1 off Also run Grafana, Loki, Tempo, VictoriaMetrics (grafana.NAME)
--no-observability FALAK_OBSERVABILITY=0 Turn it off on a re-run
--tls acme|internal FALAK_TLS acme internal = Caddy’s local CA, testing only
--repo OWNER/NAME FALAK_REPO OthmanHaba/falak GitHub repository of the release (forks)
--image-prefix PREFIX FALAK_IMAGE_PREFIX ghcr.io/<owner> Image registry prefix
--build-from-source FALAK_BUILD_FROM_SOURCE=1 off Build images locally instead of pulling them
--ref REF FALAK_REF --version or main Git ref for --build-from-source
--source-dir PATH FALAK_DEPLOY_SOURCE Use deploy files from a local checkout (offline/testing)
--http-port N / --https-port N FALAK_HTTP_PORT / FALAK_HTTPS_PORT 80 / 443 Other ports only with --tls internal (ACME needs 80/443)
--dir PATH FALAK_DIR /opt/falak Install directory
--skip-dns-check FALAK_SKIP_DNS_CHECK=1 off Do not require DNS to point at this host
--force off Continue on an unsupported OS or low resources
-h, --help Print usage

Running the installer again is safe: secrets in .env are kept, settings from the flags are updated, the stack is converged, services whose mounted config files changed are recreated, and the admin is not created twice. Use it to enable observability later:

Terminal window
curl -fsSL https://falak.sh/install.sh \
| sudo bash -s -- --domain falak.example.com --email you@example.com --observability
/opt/falak/.env settings + secrets (install.sh; never commit or share)
/opt/falak/custom.env optional extra app env (GITHUB_APP_*, mirrors, FALAK_* tuning) loaded by the app containers
/opt/falak/deploy/ compose.yml, falak-ctl, image support files (replaced on update; previous kept as deploy.prev)
/opt/falak/observability/ Loki/Tempo/Grafana/gateway configs
/opt/falak/backups/ falak-ctl backup output
/usr/local/bin/falak-ctl the operations tool

See Configuration for what goes in .env versus custom.env.

  1. Log in at https://falak.example.com with the printed password.
  2. Configure mail so invitations and e-mail alerts work.
  3. On a panel reachable from the internet, restrict sign-up with FALAK_REGISTRATION=invite or closed (anyone can create an account by default).
  4. Schedule backups and copy them off the host.
  5. Read Security hardening.
  6. Connect servers. Agent binaries come from your panel (/install/agent/linux-{amd64,arm64}), not from GitHub.

The stack runs an image registry (Docker Distribution) that the edge serves at https://registry.<domain> with a Let’s Encrypt certificate and basic auth. Docker-mode builds (Dockerfile sites, Compose services with build:) push there, and servers pull from it, pinned by digest. Only the edge can reach the registry container; images live in the registry-data volume.

  • The installer generates the settings into .env: FALAK_REGISTRY_HOST, FALAK_REGISTRY_URL, FALAK_REGISTRY_USERNAME (falak) and FALAK_REGISTRY_PASSWORD. Falak hands the credentials to builders and servers itself.
  • Create the registry.<domain> DNS record like the others. falak-ctl registry status shows its address and size and checks that it answers with its credentials; falak-ctl doctor also checks that it refuses requests without them.
  • Builder servers and external builders push to https://registry.<domain> over the internet, so they need to resolve and reach it. With --tls internal, Docker on other hosts doesn’t trust the registry’s certificate: Docker builds need --tls acme (or Caddy’s internal root trusted in each Docker daemon).
  • Installs from before the registry get FALAK_REGISTRY_* added to .env by the next falak-ctl up or falak-ctl update (after an update run by an older falak-ctl, run falak-ctl up once). Then add the registry. DNS record and check it with falak-ctl registry status.

Every Docker build pushes an image. Two jobs keep the registry from growing forever:

  • Daily cleanup of old images (falak:registry-prune in the control plane, 03:45, after the artifacts prune): deletes the images of builds whose artifact was pruned (each site keeps its newest FALAK_ARTIFACTS_KEEP builds, default 10). An image stays while a release may still run it (pending, live or kept for rollback), while its build is running or less than a day old; tags that aren’t build ids are never touched. Images of a deleted site’s builds go FALAK_REGISTRY_DELETED_SITE_GRACE_DAYS (default 7) days after the build. Preview with falak-ctl registry prune --dry-run, or run it now with falak-ctl registry prune.
  • Weekly garbage collection (/etc/cron.d/falak-registry-gc, Sunday 04:17, written by falak-ctl up and update): falak-ctl registry gc deletes the layers nothing references any more, which is what frees disk space. The registry is stopped while it runs (a push during garbage collection could lose layers), so it runs at night and is skipped (logged, tried again the next week) while an image build is queued or running, or when the control plane can’t tell. --force runs it anyway. Output goes to /var/log/falak-registry-gc.log. Set FALAK_REGISTRY_GC=0 in .env and run falak-ctl up to remove the cron entry.

--tls internal issues certificates from Caddy’s local CA, skips the DNS check and allows custom ports:

Terminal window
sudo bash install.sh --domain falak.test --email you@example.com --tls internal --http-port 8080 --https-port 9443
curl -k --resolve falak.test:9443:127.0.0.1 https://falak.test:9443/up
Terminal window
docker build -f control-plane/Dockerfile --build-arg FALAK_VERSION=dev -t falak-local/falak-control-plane:dev .
docker build -f deploy/builder.Dockerfile --build-arg FALAK_VERSION=dev -t falak-local/falak-builder:dev .
docker build --build-arg FALAK_VERSION=dev -t falak-local/falak-edge:dev deploy/edge

Or let the installer do it with --build-from-source [--ref REF].

Symptom Fix
DNS does not point at this host Create the printed A/AAAA records and wait (dig +short falak.example.com). With Cloudflare, use DNS only.
port 80 is in use Stop the other web server: systemctl disable --now nginx apache2 caddy.
pulling images … failed The release is not published, or GHCR packages are private (docker login ghcr.io).
the stack did not become healthy The installer prints the last logs; then falak-ctl doctor, falak-ctl logs control-plane.
FALAK_EDGE_SUBNET … overlaps Pick another private /24 in .env (FALAK_EDGE_SUBNET=…) and re-run. The app trusts proxy headers only from that subnet.
Docker build fails at push (lookup registry.falak.local … no such host, 401, x509) The install has no built-in registry yet, or its DNS record is missing: run falak-ctl up (adds FALAK_REGISTRY_*), create the registry.<domain> record, then falak-ctl registry status. x509 with --tls internal: see the built-in image registry.
Browser shows a certificate error falak-ctl logs edge for ACME errors. Ports 80/443 must be reachable from the internet. Let’s Encrypt rate limits apply to repeated reinstalls.