Setting up sfp server

This page walks you through initializing and starting a self-hosted sfp server. Complete the Prerequisites before you begin.


[1] Create a server.json on your local machine

Save this file as ./server.json in the directory where you'll run sfp server init. Every field is explained in the Reference → Properties page; defaults handle the most common install path.

{
  "domain": "sfp.yourcompany.com",
  "workers": 2,
  "image_fqdn": "source.flxbl.io/flxbl/sfp-pro-v3/sfp-server",
  "image_tag":  "v3-latest",
  "secrets": {
    "DOCKER_REGISTRY_TOKEN": "<paste-token-from-step-1.1>"
  }
}

image_fqdn and image_tag are explicit overrides today. They will fold into the production cadence default in a future release; drop them from server.json at that point.

[1.1] Generate DOCKER_REGISTRY_TOKEN (required)

sfp server init will fail if this token isn't provided. Get one at source.flxbl.io → User Settings → Applications — Generate New Token, All repos + Org access, package: Read scope. Contact flxbl for source.flxbl.io access if you don't have it yet.

Provide the token to sfp server init via one of:

  • server.json secrets block (shown above) — simplest for prod installs

  • Environment variable: export DOCKER_REGISTRY_TOKEN=<token> before running init

  • Secrets provider: --secrets-provider infisical or aws-secretsmanager (see Reference)


[2] Configure TLS

DNS prerequisite. All three options below assume your domain (e.g. sfp.yourcompany.com) already resolves to the server's public IP. If you haven't set that up — point an A record at the server's IP — see Prerequisites → Network & DNS for the exact Cloudflare / Route 53 / GoDaddy table (TTL, proxy mode, dig verification). Cloudflare users: keep the record DNS-only / grey cloud; the orange-cloud proxy intercepts the ACME challenge and breaks Let's Encrypt.

SFP Server uses Caddy as its reverse proxy. Caddy handles TLS termination and supports four modes, configured via the --tls-mode flag during sfp server init:

Mode

--tls-mode value

Best for

What you provide

Bring Your Own Cert

cloudflare (default)

Enterprise / private networks, corporate CA, Cloudflare Origin CA

Base64-encoded PEM cert + key

Let's Encrypt

letsencrypt

Public-facing servers, quick evaluation

Ports 80 + 443 open, public DNS

Custom / On-demand

custom

On-demand TLS via Caddy

Nothing — Caddy manages certs

Behind Load Balancer

none

Existing infrastructure (ALB, F5, NGINX) that already terminates TLS

Nothing — server runs HTTP internally

[2.a] Bring Your Own Certificate (--tls-mode cloudflare, default)

If the server is on a private network or you manage certificates through your own PKI / corporate CA:

  1. Obtain a TLS certificate and private key for your domain (from your internal CA, a commercial CA, or Cloudflare Origin CA)

  2. You need two files in PEM format:

    • Certificate (full chain recommended): origin.pem

    • Private key: origin-key.pem

  3. Base64-encode both files:

  4. During sfp server init, provide them as the ORIGIN_CERT and ORIGIN_KEY secrets via one of:

    • JSON config file (--config-file) — under the secrets block, same as DOCKER_REGISTRY_TOKEN

    • Environment variables: export ORIGIN_CERT and ORIGIN_KEY before running init

    • Secret provider (Infisical, AWS Secrets Manager)

  5. The CLI decodes the base64 values and writes origin.pem / origin-key.pem into the tenant's certs/ directory automatically

If you have .crt + .key files instead of .pem, they are the same format — just rename before encoding:

Advantages: No inbound ports required. Works on private networks and air-gapped environments. Fits into existing corporate certificate management workflows.

Fallback: If you skip providing the secrets, the CLI will warn you and you can manually place origin.pem and origin-key.pem in the {tenantDir}/certs/ directory before starting services.

[2.b] Automatic TLS via Let's Encrypt (--tls-mode letsencrypt)

If the server is internet-accessible and you prefer automated certificate management:

  1. Ensure your domain's DNS resolves to the server's public IP

  2. Open ports 80 and 443 inbound:

  3. No certificate files are needed — Caddy obtains and renews certificates via ACME

Caddy will fail to obtain a cert if (a) DNS hasn't propagated to the server's public IP, or (b) port 80 is blocked by an upstream firewall. Verify both before running sfp server init.

[2.c] Behind a Load Balancer (--tls-mode none)

If your existing infrastructure (AWS ALB, Azure Application Gateway, F5, NGINX) already handles TLS:

  1. Configure your load balancer to terminate TLS on port 443

  2. Forward traffic to the Caddy HTTP listener (normally port 80 in production; use the tenant's published HTTP port for dev/smoke environments)

  3. Ensure the LB sets or preserves X-Forwarded-Proto: https, X-Forwarded-Host, and X-Forwarded-For headers

  4. During sfp server init, pass --tls-mode none

Caddy still runs (it is required for auth routing), but serves HTTP only — no TLS termination.

For GitHub.com, Azure DevOps, or other public SaaS webhook providers, the webhook URL must be reachable from the public internet over HTTPS. A server that is reachable only through SSH, VPN, a bastion host, or ProxyJump is not reachable by GitHub.com webhooks.

This does not mean exposing the sfp server host directly. Keep the server backend private, and expose only the public webhook payload URL unless you intentionally want to publish the full sfp UI/API:

For private servers, use one of these ingress patterns:

Pattern
How it works
Required checks

Public ALB / load balancer

SaaS webhook provider calls the public HTTPS listener; the load balancer forwards privately to Caddy

Recommended default on AWS or when a customer load balancer already exists

Public reverse proxy / tunnel

SaaS webhook provider calls a public HTTPS proxy URL; the proxy opens an outbound tunnel or forwards to the private server

Use when the server cannot accept inbound internet traffic

API Gateway

SaaS webhook provider calls a public API Gateway HTTPS endpoint; API Gateway routes through VPC Link / private integration to an internal ALB/NLB, then to Caddy

Use only when API Gateway features are required

See Webhook ingress for private servers for the security model, provider-specific checks, and troubleshooting guidance. In all patterns, preserve the raw request body and provider signature headers.


[3] Initialize and start

You can run init and start remotely from your workstation (via SSH) or locally on the server itself. Both options produce the same result.

[3.a] Remote (from your workstation)

[3.b] Local (on the server itself)

SSH into the server and install the sfp CLI. Replace <version> with the tag you want from source.flxbl.io/flxbl/sfp-pro-v3/releases (e.g. v3.5.1):

Gitea release-asset URLs require either a pinned tag (/releases/download/<tag>/<asset>) or a literal asset name (/releases/latest/download/<literal>). Because the .deb / .rpm filenames embed the version (sfp-pro_3.5.1_linux_amd64.deb), pin the tag rather than using latest. Browse the releases page above for the current version.

Then run init and start without --ssh-connection / --identity-file:

All subsequent commands (start, stop, logs, update) work the same way — drop the --ssh-* flags when running locally.

Quick evaluation without a domain? Use --mode dev to skip TLS and domain requirements. The server will be accessible at http://<server-ip>:3029. Pass the server IP as --domain.

[3.i] What init does:

  1. Checks Docker + Docker Compose are installed

  2. Creates <base-dir>/tenants/<tenant>/

  3. Collects secrets (from --config-file, env, or --secrets-provider)

  4. Self-hosted Supabase: generates Postgres password + JWT secret + anon/service keys

  5. Cloud Supabase: tests DB connectivity (fail-fast before writing state)

  6. Writes .env, renders docker-compose.yml, configures Caddy

  7. Runs schema migrations

  8. Creates default admin user

  9. Persists admin credentials to a permissioned file (or stdout if --print-credentials)

Health response: HTTP 200 with status: healthy plus a components map (api, database, metrics, logs, flows, registry). HTTP 503 when database or flows (Hatchet) is down — wire that into your LB probe.

[3.c] Enable auto-restart on the server


[4] Log in

Open https://sfp.yourcompany.com in a browser and sign in with the admin email and password printed during init (or from the credentials.json file on the server). Use the Login via Email option.


[5] Configure your git provider

With the server up and you logged in, connect your git provider from the UI. Nothing here was needed at init. There are two entry points to the same configuration:

  • Onboarding wizard — the 8/8 Onboarding panel walks you through App Integration, Dev Hub, Project Readiness, and Configure Webhooks per feature.

  • Settings → Integrations (/settings/integrations) — the durable surface to add, view, and re-scope integrations. Source control providers (GitHub, Azure DevOps, GitLab) live under SOURCE CONTROL.

Credentials are stored encrypted at rest and can be global or per-project.

[5.a] GitHub (github.com)

In Settings → Integrations → GitHub, install the prebuilt flxbl-cloud GitHub App (github.com/apps/flxbl-cloud). You authorize it on GitHub and pick the repositories; no keys to copy. This is the whole setup for cloud GitHub.

[5.b] GitHub Enterprise Server

GHES has no prebuilt app, so you register your own GitHub App and enter its App ID + private key in Settings → Integrations → GitHub. Create the App and grant the required permissions per Connecting GitHub as a CI/CD provider.

[5.c] Azure DevOps

In Settings → Integrations → Azure DevOps, enter a service principal (Microsoft Entra app registration): organization_url, client_id, client_secret, tenant_id (optional entra_authority_url for sovereign clouds). Once connected it shows as Service Principal. sfp-server subscribes these service hooks: git.push, git.pullrequest.created, git.pullrequest.updated, git.pullrequest.merged, ms.vss-code.git-pullrequest-comment-event.

[5.d] Webhooks

The git integration creates the repository webhooks (or Azure DevOps service hooks) automatically. Review and re-sync them under Settings → Webhooks. The server's webhook endpoint must be reachable from the provider — see Webhook ingress for private servers.

[5.e] GitHub Packages (npm)

Only if your pipelines pull or publish npm packages from npm.pkg.github.com: add a token with read:packages + write:packages under Settings → Integrations → npm Registry. GitHub Apps cannot operate on GitHub Packages, so this is a separate registry-auth token.


[6] Configure login

The admin email/password login works immediately after init. To let your team sign in with their own accounts, enable one login provider. This is set by AUTH_PROVIDER (github / azure / saml, default github).

Provider
How to enable

GitHub OAuth

Create a GitHub OAuth App (callback https://<your-domain>/auth/v1/callback), set GITHUB_OAUTH_CLIENT_ID + GITHUB_OAUTH_CLIENT_SECRET and GITHUB_OAUTH_ENABLED=true in the server .env, then restart.

Azure OAuth

Register a Microsoft OAuth app, set AZURE_OAUTH_CLIENT_ID / _SECRET / _TENANT_ID in .env, restart.

SAML SSO

Okta, Entra ID, or any SAML 2.0 IdP — follow SAML Authentication.

Login OAuth is wired into the auth layer (GoTrue) at container start, so it needs an .env edit and a restart — it is not a pure UI toggle. The login provider is independent of the git provider in Section 5; even if both are GitHub, they are two separate registrations (OAuth App for login, GitHub App for repository work).

Note on attribution: issues, release requests, and PRs raised on a user's behalf run under that signed-in user's OAuth token, so they appear under the user's name. Machine-driven work runs under the git provider's app / service principal from Section 5.


Next steps

Page
What it covers

Start, stop, logs, and other lifecycle commands

Image bumps, drains, migrations via sfp server update

Properties, secrets, CLI flags, cloud Supabase setup

Common issues, fixes, and hardening recommendations

GitHub App registration + permissions reference (deep dive for [5.b] GHES)

Configure SAML SSO with Entra ID, Okta, or any SAML 2.0 IdP

Last updated

Was this helpful?