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
server.json on your local machineSave 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)
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.jsonsecretsblock (shown above) — simplest for prod installsEnvironment variable:
export DOCKER_REGISTRY_TOKEN=<token>before running initSecrets provider:
--secrets-provider infisicaloraws-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,digverification). 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)
--tls-mode cloudflare, default)If the server is on a private network or you manage certificates through your own PKI / corporate CA:
Obtain a TLS certificate and private key for your domain (from your internal CA, a commercial CA, or Cloudflare Origin CA)
You need two files in PEM format:
Certificate (full chain recommended):
origin.pemPrivate key:
origin-key.pem
Base64-encode both files:
During
sfp server init, provide them as theORIGIN_CERTandORIGIN_KEYsecrets via one of:JSON config file (
--config-file) — under thesecretsblock, same asDOCKER_REGISTRY_TOKENEnvironment variables: export
ORIGIN_CERTandORIGIN_KEYbefore running initSecret provider (Infisical, AWS Secrets Manager)
The CLI decodes the base64 values and writes
origin.pem/origin-key.peminto the tenant'scerts/directory automatically
If you have
.crt+.keyfiles 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.pemandorigin-key.pemin the{tenantDir}/certs/directory before starting services.
[2.b] Automatic TLS via Let's Encrypt (--tls-mode letsencrypt)
--tls-mode letsencrypt)If the server is internet-accessible and you prefer automated certificate management:
Ensure your domain's DNS resolves to the server's public IP
Open ports 80 and 443 inbound:
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)
--tls-mode none)If your existing infrastructure (AWS ALB, Azure Application Gateway, F5, NGINX) already handles TLS:
Configure your load balancer to terminate TLS on port 443
Forward traffic to the Caddy HTTP listener (normally port 80 in production; use the tenant's published HTTP port for dev/smoke environments)
Ensure the LB sets or preserves
X-Forwarded-Proto: https,X-Forwarded-Host, andX-Forwarded-ForheadersDuring
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:
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.
Do not use SSH reachability as the webhook test. ssh -J <bastion> <server> only proves that an operator can administer the server. It does not prove that GitHub.com can deliver a webhook. Test the exact configured payload URL with the provider's delivery tooling, such as GitHub Recent deliveries / Redeliver. A manual POST to the exact webhook path can prove routing, but only a real provider delivery proves signature validation end to end.
[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/.rpmfilenames embed the version (sfp-pro_3.5.1_linux_amd64.deb), pin the tag rather than usinglatest. 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 devto skip TLS and domain requirements. The server will be accessible athttp://<server-ip>:3029. Pass the server IP as--domain.
[3.i] What init does:
Checks Docker + Docker Compose are installed
Creates
<base-dir>/tenants/<tenant>/Collects secrets (from
--config-file, env, or--secrets-provider)Self-hosted Supabase: generates Postgres password + JWT secret + anon/service keys
Cloud Supabase: tests DB connectivity (fail-fast before writing state)
Writes
.env, rendersdocker-compose.yml, configures CaddyRuns schema migrations
Creates default admin user
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).
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
.envedit 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
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?