Skip to main content

Build and Packaging

Single source of truth for the make build/test/package system — commands, workflows, and how they compose. New to the project? Read the Contributor Guide first; it covers first-time setup and the day-to-day PR workflow this doc doesn't.

Prerequisites

Exact tool versions are developer-environment.md's job, not this doc's — see Required Runtimes and Optional Tools there for the full, version-pinned list (kept in sync with .tool-versions). Everything below assumes those are already installed; this table only covers what's specific to building/packaging on top of them:

ToolPurpose
zipCF and Windows release archives
swagoptional — OpenAPI documentation generation

Run make from the repository root

All make commands must be invoked from the repository root. GNU make has no search path for locating its top-level makefile (-I only affects include directives inside makefiles), so invocations from subdirectories fail — and building components directly instead is unsafe: the root targets carry generated-file dependencies that ad-hoc builds skip. For example, running go build from src/jetstream omits the generated extra_plugins.go, which produces a backend without the Cloud Foundry plugin that crashes at startup when deployed as a Cloud Foundry application.

Either make or gmake works — this Makefile targets GNU Make 3.81+ (see Platform-specific notes in developer-environment.md), so macOS's stock /usr/bin/make (3.81, frozen there for licensing reasons — Apple won't ship GPLv3) and a newer gmake (e.g. via brew install make) behave identically; verified byte-for-byte across make help, make dump version, and representative recipes. Whichever binary you invoke at the top level is what every recursive self-invocation inside the Makefile reuses too ($(MAKE), never a hardcoded make), so there's nothing to keep consistent by hand — just pick one and it propagates correctly on its own.

Operations Reference

Building

CommandWhat it doesOutput
make buildBuild frontend + all backend platformsdist/frontend/browser/, dist/bin/jetstream-{os}-{arch}
make build frontendBuild frontend only (production)dist/frontend/browser/
make build backendCross-compile backend for all platformsdist/bin/jetstream-{os}-{arch}
make build backend PLATFORM=linux/amd64Build backend for one platformdist/bin/jetstream
make build cfBuild frontend + linux/amd64 backend (CF deploy)dist/frontend/browser/, dist/bin/jetstream

Testing

CommandWhat it does
make testRun all tests (frontend + backend)
make test frontendFrontend tests only (Vitest)
make test backendBackend tests only (Go)
make test e2ePlaywright E2E tests against deployed instance

E2E tests need CF credentials, supplied via secrets.yaml (plaintext, gitignored) or its encrypted form secrets.yaml.enc (committed) — see Secrets Management for the zero-plaintext encrypt/decrypt workflow. make clean repo (below) keeps secrets.yaml by default rather than sweeping it with everything else untracked.

Quality Gates

CommandWhat it does
make checkLint + gate (default — ESLint + go fmt/vet + golangci-lint + Vitest)
make check lintLint checks (ESLint + go fmt + go vet + golangci-lint on both Go modules). Requires golangci-lint installed. Note: go fmt may modify files.
make check gateFull pre-push quality gate — mirrors what CI runs on each PR (ESLint + Vitest frontend tests + Go unit tests). Run this before every push.
make check testsUnit tests only
make check coverageFrontend unit tests with coverage (Vitest). No Go coverage.
make check e2ePlaywright E2E core tests

ESLint and Vitest need no separate install — both are plain devDependencies in package.json, covered by bun install. golangci-lint is the only tool here that's external to the JS toolchain and needs its own install step (see developer-environment.md).

Run make check gate before any push — it mirrors what CI runs on each PR. make check e2e requires a running Stratos instance (local or deployed); point it at a specific deployment via the E2E_BASE_URL environment variable (defaults to the local dev server at https://localhost:5540).

make test e2e/make check e2e also need Playwright's browser binaries — bun install pulls in the @playwright/test package but does not download the browsers themselves. One-time setup: bunx playwright install chromium (the default E2E_BROWSERS target); add firefox/webkit if you'll run with E2E_BROWSERS=firefox, webkit, or all.

E2E recipe variables

make check e2e and make test e2e accept these recipe-level variables to control which browsers run and what artifacts get captured. All five compose freely — set any combination on one command line.

VariablePurposeExample
E2E_BROWSERSPick which Playwright projects run. Comma-separated list (chromium,firefox,webkit), or all for every project in playwright.config.ts. Empty → chromium only (today's default).E2E_BROWSERS=firefox
E2E_TRACEForce trace capture. Values: on, off, retain-on-failure, on-first-retry. Empty → uses playwright.config.ts default (on-first-retry).E2E_TRACE=on
E2E_VIDEOForce video capture. Values: on, off, retain-on-failure, on-first-retry. Empty → uses playwright.config.ts default (retain-on-failure).E2E_VIDEO=on
E2E_SCREENSHOTSForce screenshot capture. Values: on, off, only-on-failure. Empty → uses playwright.config.ts default (only-on-failure).E2E_SCREENSHOTS=on

The cross-cutting DRYRUN=yes variable (also used by make bump, see Version Management below) lists tests that would run without executing them — useful for checking browser and filter combinations without spinning up a Stratos instance.

Common invocations:

# Default — chromium only, config-default artifact handling
make check e2e

# Pick a single non-default browser
make check e2e E2E_BROWSERS=firefox

# Multiple browsers
make check e2e E2E_BROWSERS=chromium,webkit

# Every project in playwright.config.ts
make check e2e E2E_BROWSERS=all

# Capture full artifacts on every test (answers "are there images to look at?")
make check e2e E2E_TRACE=on E2E_VIDEO=on E2E_SCREENSHOTS=on

# Combine browser selection and artifact capture
make check e2e E2E_BROWSERS=all E2E_TRACE=on E2E_VIDEO=on

# List tests instead of executing (fast, no server spin-up)
make check e2e DRYRUN=yes

Validation is delegated to Playwright — unknown browser names and unsupported artifact values produce clear errors from playwright test itself.

Packaging and Release

CommandPrerequisitesWhat it doesOutput
make stagemake buildStage artifacts for local testingdist/install/ with run.sh
make release cfmake build (linux/amd64)Create CF-pushable zipdist/stratos-cf-{VERSION}.zip
make release githubmake buildCreate all release archivesdist/release/ (7 archives)
make releaseAll of the aboveCreate both CF zip and GitHub archivesBoth

Every artifact-producing release invocation finishes by staging the cf/korifi zip (when present) into dist/release/ and regenerating a single SHA256SUMS over everything there. ./build/create-checksums.sh remains callable standalone for the exceptional regen case (fix an asset after make unpublish, re-checksum, re-publish).

Release notes

Notes accumulate continuously as per-PR fragment files in changelog.d/ — each PR adds its own NNNN-<slug>.md (./build/release-notes.sh new) with entries under [Breaking Changes] / [Features] / [BugFixes] / [Chores] / [Security Updates] headers. make stamp tag assembles them (sections in that order, populated sections only) into the tag body, make publish reuses the tag body as the GitHub release notes, and make sweep clears the directory for the next cycle. See changelog.d/README.md for authoring details and ./build/release-notes.sh assemble for a preview.

Publishing a release

make owns the whole lifecycle; any runner (a human terminal, GitHub Actions, Concourse) is a thin caller of the same targets. Credentials come from the environment (GH_TOKEN/GITHUB_TOKEN, or a prior gh auth login) — never from the command line.

CommandWhat it does
make stamp tag [VERSION=X.Y.Z]Create + push the annotated release tag (TAG_REMOTE, default origin); the tag body carries the release notes assembled from changelog.d/ fragments (see Release notes above)
make publish [DRAFT=yes] [TAG=vX.Y.Z]gh release create --verify-tag + upload dist/release/*; --prerelease derived from an alpha/beta/rc part in the tag (dev.N tags can be full releases)
make unpublish TAG=vX.Y.ZDelete the GitHub release and its assets (echoes what it will delete first; the tag survives)
make stamp untag TAG=vX.Y.ZDelete the tag, local + remote (echoes first)
make sweepRemove the changelog.d/ fragments the release consumed (commit rides the next PR)

VERSION and TAG hold the same underlying value — whether a v prefix is present is a per-tool convention, not a semver rule, and the Makefile round-trips whatever you give it rather than forcing either way: VERSION's value gets baked byte-for-byte into the binary (main.appVersion), so whatever prefix you pass shows up in the running app's own version string. make bump writes package.json without a v (that's the project's convention, matching semver.org — and where git/GitHub tags conventionally do carry one, since that's their own ecosystem's convention, handled at the tag layer via TAG's default: v + the current VERSION with build metadata stripped, 5.0.0-dev.142+build...v5.0.0-dev.142). make stamp tag (creating a new tag) validates the result looks like vX.Y.Z[-prerelease], but publish/unpublish/stamp untag just reference whatever tag already exists — any value GitHub accepts works. Release notes come from NOTES=<file> when given, else the annotated tag body (a pointer line when no fragments existed at tag time). All targets honor DRYRUN=yes.

Complete lifecycle, start to finish (rollback runs the release/tag verbs in reverse — release first, so a half-done rollback never orphans the tag). A real release normally starts with make bump <level> instead of an explicit VERSION= — see Version Management below for what each bump level does; this example uses VERSION= throughout so it's copy-pastable without deciding a bump level first.

Word order matters when chaining bump into the same invocation as build/release. Make runs multiple goals on one command line strictly in the order given — bump, build, and release have no prerequisite relationship forcing a different order, so Make just does what you typed. make bump dev build release cf bumps first, so the build/release pick up the new version. make build release cf bump dev builds and releases first with the still-old version, then bumps — package.json ends up one version ahead of what you just shipped, silently. If you're appending bump dev to a command you already typed rather than composing it fresh, put it at the front, not the end. The lifecycle below sidesteps the question entirely by using an explicit VERSION= instead of chaining bump:

make build VERSION=X.Y.Z
make release cf github VERSION=X.Y.Z # artifacts + SHA256SUMS
make stamp tag VERSION=X.Y.Z # create + push tag vX.Y.Z (notes in body)
make publish VERSION=X.Y.Z # gh release create + assets
make sweep # drop consumed fragments

make unpublish TAG=vX.Y.Z # rollback: release first...
make stamp untag TAG=vX.Y.Z # ...then the tag

Development

CommandWhat it does
make installInstall dependencies (bun install)
make dev frontendStart frontend dev server with hot reload
make dev backendStart backend dev server
make stageStage production build into dist/install/ for local testing

Run dev frontend and dev backend in two separate terminal windows — neither backgrounds itself, and the frontend dev server proxies API calls to the backend port (proxy.conf.cjs), so both need to be running at once:

# Terminal 1
make dev backend

# Terminal 2
make dev frontend

Frontend changes are liveng serve watches and hot-reloads on save, no restart needed. Backend changes are notdev.backend builds once (only if the binary is missing or stale for this platform) and then runs; editing Go source needs a stop (Ctrl-C) and a fresh make dev backend to rebuild and pick up the change.

make stage isn't a substitute for make dev, despite both being "run it locally" — they answer different questions. make dev is live development: hot reload, unoptimized, source maps intact. make stage packages an already-built production artifact (make build's output) into dist/install/ with a run.sh launcher, so you can verify the actual shippable build runs standalone — no dev-server proxying, no watch mode. Use stage as a pre-release sanity check, not for day-to-day iteration.

stage needs a single, host-matching backend binary — not what a bare make build produces. build.backend branches on whether PLATFORM is set:

CommandPLATFORMOutput
make build / make build backendunset (default)Cross-compiles all six supported platforms via cross-compile.shdist/bin/jetstream-{os}-{arch} (e.g. jetstream-linux-amd64)
make build backend PLATFORM=<os>/<arch>setBuilds one platform → plain dist/bin/jetstream
make dev backend(n/a)Builds for the host platform only, automatically, if dist/bin/jetstream is missing or wrong for this machine

install-local.sh (make stage) looks for exactly the unqualified dist/bin/jetstream — the per-platform-named files from a bare make build don't satisfy it, and it can't run a binary built for a different OS/arch than the host anyway. So make build then make stage fails with no matching binary; make build backend PLATFORM=<host os>/<host arch> (or just make dev backend, then make stage) is what you want instead.

Cross-compiling works in either direction, any host arch. build.backend hardcodes CGO_ENABLED=0, so it never invokes a C cross-compiler — Go's own toolchain ships every GOOS/GOARCH combination self-contained, making the build host-arch-independent by construction: an Apple Silicon (arm64) machine cross-compiles linux/amd64 exactly as readily as an amd64 machine cross-compiles linux/arm64, verified directly. The one exception is build.korifi, which deliberately uses CGO_ENABLED=1 + zig for a static-cgo build — that path does need a real (portable) C cross-compiler, which is precisely why it reaches for zig instead of the host's own cc.

Documentation (docs/ → HTML or PDF/epub)

docs/ is plain markdown — read as-is on GitHub, but two commands render it without the raw formatting codes:

CommandWhat it does
make dev websiteDocusaurus dev server with hot reload — reads docs/ directly (path: '../docs' in website/docusaurus.config.js), no separate conversion step
make build websiteBuild the HTML site into website/build/
make build bookletsRender curated docs/ subsets to standalone PDF/epub via Quarto — see docs/booklets/README.md for how a booklet's chapter list ("spine") is defined

Booklets need the quarto tool (see developer-environment.md); the website only needs bun (already required).

Booklet source (docs/booklets/) lives inside docs/ but is excluded from the published site (exclude in docusaurus.config.js) — CI renders booklets on every PR as a downloadable artifact for review, not a publish target. Preview one locally with quarto preview (see docs/booklets/README.md).

Clean

CommandWhat it does
make cleanRemove all build output (frontend, backend, release artifacts)
make clean frontendRemove frontend build only (dist/frontend/, .angular)
make clean backendRemove backend binaries only (dist/bin/)
make clean distRemove everything including node_modules
make clean repoFull reset — git clean -fdx, fresh-clone state. Keeps .env, secrets.yaml, and site.mk (add RM_SITE=yes to drop site.mk too)

Security & Dependencies

CommandWhat it does
make auditDefault set: frontend + website + backend + actions + packages + secrets
make audit frontendbun audit, root workspace
make audit websitebun audit, website/ workspace
make audit backendgosec + trivy + govulncheck
make audit actionszizmor SAST over .github/workflows/
make audit packagesosv-scanner over every lockfile and go.mod
make audit secretsgitleaks working-tree scan
make audit summaryHigh/moderate/low totals only — fast triage between full runs
make audit tests / tree / history / licensesNon-default modes — see developer-environment.md for what each covers and why it's opt-in
make audit modrot / semgrep / codeqlNon-default, manual-run scanners — see developer-environment.md for tool install
make audit sarifsarif-tools summary across dist/*.sarif
make audit uploadPush dist/*.sarif to GitHub code scanning
make outdatedList outdated direct deps in both stacks
make outdated frontendbun outdated
make outdated backendgo list -m -u all, filtered to upgradable lines
make deps dependabotList open dependency PRs (requires gh auth + dependencies label)

These wrap the underlying scanners — they do not gate the build. Decide which findings to act on by reading the output. See developer-environment.md for tool installation.

The bare make security target was retired in favour of make audit backend (combined gosec + trivy + govulncheck). The old name now prints a renamed-to message via deprecated.mk.

Version Management

Stratos follows SemVer 2.0.0 with a full prerelease lifecycle and automatic build metadata on every bump.

Lifecycle stages (labels only — no auto-promotion; each stage is selected explicitly):

dev → alpha → beta → rc → prerelease → release

Semver bumps (strip any prerelease, then increment the component):

CommandExample inputResult
make bump patchv4.9.3-dev.38v4.9.4
make bump minorv4.9.3-dev.38v4.10.0
make bump majorv4.9.3-dev.38v5.0.0

Prerelease bumps (operate on the current semver core — create .1 if the stage is new, otherwise increment the counter):

CommandExample inputResult
make bump devv4.9.3v4.9.3-dev.1
make bump devv4.9.3-dev.38v4.9.3-dev.39
make bump alphav4.9.3-dev.38v4.9.3-alpha.1
make bump betav4.9.3-alpha.2v4.9.3-beta.1
make bump rcv4.9.3-beta.1v4.9.3-rc.1
make bump rcv4.9.3-rc.1v4.9.3-rc.2
make bump prereleasev4.9.3-rc.2v4.9.3-prerelease.1

To strip the prerelease suffix and finalize a release, use FINAL=strip on the release verb — see Finalizing a release below.

Automatic build metadata. Every make bump appends +build.YYYYMMDD.SHORT-SHA per SemVer 2.0.0, e.g. v4.9.3-dev.42+build.20260410.79d3982a. Build metadata is ignored by version precedence rules but provides traceability from binary to source.

Explicit set. For an arbitrary version string, call the script directly:

./build/version-bump.sh set v5.0.0-rc.1

Preview a bump without writing files: use DRYRUN=yes (see Cross-cutting variables below).

Metadata and Diagnostics

CommandWhat it does
make stamp frontendGenerate build-info.ts with version and git metadata
make dump versionPrint resolved semver, VCS metadata, and Go ldflags
make dump actionsList all registered verb+modifier pairs

Cross-cutting variables

These variables compose with any verb and affect behavior without changing the command shape.

VariableWhat it does
DRYRUN=yesPreview actions without executing. Wired to bump (prints the new version without writing package.json) and to check e2e / test e2e (passes --list to Playwright, listing tests without running them).
FINAL=stripStrip prerelease suffix from the version (persisted to package.json), then re-invoke Make with the remaining goals. Useful as a one-shot finalize-then-package on the release verb.
VERSION=...Override the version from package.json for this invocation only (not persisted).
PLATFORM=os/archOverride backend build platform. Syntax is Go's own GOOS/GOARCH values, lowercase, slash-separated — not a Stratos-specific format. os: linux, darwin, windows. arch: amd64, arm64. Those are the six combinations this project builds/tests (see Supported Platforms in developer-environment.md); Go itself supports more GOOS/GOARCH pairs than that if you need one we don't list.

Finalizing a release

FINAL=strip on the release verb first strips the prerelease suffix from package.json (via version-bump.sh bump release) and then re-executes the original make goals. The typical finalize-and-package flow:

# Current: v5.0.0-rc.2+build...
make release cf FINAL=strip
# → Strips prerelease: v5.0.0
# → Re-invokes: make release cf
# → Produces: dist/stratos-cf-v5.0.0.zip

FINAL=strip errors out if the current version is already a release (no prerelease to strip). The only supported value is strip; any other value errors out.

Preview a bump

make bump dev DRYRUN=yes
# Prints: v4.9.3-dev.43+build.20260410.79d3982a
# package.json is not modified.

Common Workflows

Local development:

make build
make stage
dist/install/run.sh

CF deployment:

# Bump dev version for a unique artifact name, then build + package:
make bump dev
make build release cf

# Finalize a prerelease and package in one shot:
make build release cf FINAL=strip

# Explicit version override (not persisted to package.json; no v prefix):
make VERSION=5.0.0 build release cf

# Deploy (via site.mk or manually — see Site-Specific Overrides below):
cf target -o system -s stratos
cf push -f dist/cf-package/manifest.yml -p dist/stratos-cf-{VERSION}.zip

The cf modifier automatically sets PLATFORM=linux/amd64 for backend compilation. The version can be overridden without editing package.json.

info

cf push -p does not read manifest.yml from inside the zip — the manifest must be passed separately with -f or be in the current directory.

GitHub release (automated via CI):

make build
make release github

Version override:

Any make target respects VERSION= to override the version from package.json. No v prefix — VERSION is bare semver, unlike TAG (see Publishing a release above):

make VERSION=5.0.0-rc.1 build release cf
make VERSION=5.0.0 dump version

Site-Specific Overrides (site.mk)

The Makefile supports site-specific customization through an optional site.mk file. This file is loaded via -include site.mk at the end of the Makefile, so it can extend or override any variable or target.

site.mk is gitignored — it is never committed. A site.mk.example is provided as a starting point.

When to use site.mk

Use site.mk for operations that depend on your deployment environment:

  • Deploy targets (make deploy cf) with org/space configuration
  • Environment-specific variable overrides
  • Custom CI or automation targets
  • Integration with site-specific tooling

Do not put secrets in site.mk. Use environment variables or a secrets manager instead.

Getting started

cp site.mk.example site.mk
# Edit site.mk to match your environment

Adding help text

If your site.mk defines a site-help target, make help will include it automatically:

.PHONY: site-help
site-help:
@echo ""
@echo "Site-specific:"
@echo " make deploy cf Push to current CF target"

Without a site-help target, make help shows a generic note that site.mk exists. Without site.mk at all, nothing extra is shown.

Design pattern

The Makefile uses a verb + modifier pattern where order does not matter:

make <verb> [modifier...]
make build cf # verb=build, modifier=cf
make cf build release # same modifiers, different order — same result

Modifiers are no-op targets that set flags. The cf modifier forces PLATFORM=linux/amd64 for build targets. Site-specific targets follow the same pattern — site.mk adds new verbs (like deploy) that compose with existing modifiers.

See site.mk.example for the full deploy target pattern.

Validation Behavior

Release targets check for required artifacts before proceeding. If something is missing, they print what's needed and exit:

ERROR: Frontend build not found at dist/frontend/browser/
Run: make build frontend
ERROR: Backend binary not found at dist/bin/jetstream
Run: make build backend
ERROR: Cross-compiled binaries missing: jetstream-linux-arm64 jetstream-darwin-amd64
Run: make build backend

Packaging never auto-builds. This avoids the old problem where unwanted build steps blocked packaging.

Migration from Old Commands

OldNew
bin/packagemake build && make release cf
bin/package --skip-buildmake release cf (validates artifacts exist)
build/package.shmake release github
make build-frontendmake build frontend
make build-backendmake build backend PLATFORM=linux/amd64
make build-backend-allmake build backend
make packagemake release
make dev-frontendmake dev frontend
make dev-backendmake dev backend
make install (old)make install (unchanged — installs dependencies)
make clean-devmake clean
make clean-deepmake clean dist
make debug-versionmake dump version

Version and Build Metadata

The frontend and backend are built independently and may have different build dates and VCS identifiers. The package itself carries a unified version.

Package-level

VariableSourceDescription
VERSIONpackage.json (or env override)Unified package version

Frontend build metadata

Captured at prebuild via build/store-git-metadata.js into .stratos-git-metadata.json:

FieldDescription
projectRemote origin URL
branchBranch name at build time
commitFull commit SHA at build time

Backend build metadata

Injected via Go ldflags at compile time:

VariableInjected asDescription
VERSIONmain.appVersionPackage version
BUILD_DATEmain.buildDateUTC timestamp at compile time
GIT_COMMITmain.gitCommitShort commit SHA at compile time

Build environment overrides

VariableUsed byDefaultDescription
PLATFORMmake build backendHost OS/archTarget platform (linux/amd64, darwin/arm64, etc.)
VERSIONAll targetsFrom package.jsonOverride version string

Platform detection uses uname and normalizes to Go conventions (darwin, linux, amd64, arm64). The PLATFORM variable accepts any separator: linux/amd64, linux-amd64, or linux_amd64.

Package Contents

install (local testing)

dist/install/
bin/jetstream # symlink -> dist/bin/jetstream
ui/ # symlink -> dist/frontend/browser/
config.properties # copy from src/jetstream/config.example
templates/ # copy from src/jetstream/templates/
plugins.yaml # copy from src/jetstream/plugins.yaml
run.sh # wrapper that sets UI_PATH, TEMPLATE_DIR, execs jetstream

release cf

dist/cf-package/ (zipped as stratos-cf-{VERSION}.zip)
jetstream # linux binary
ui/ # frontend assets
config.properties # from deploy/cloud-foundry/
plugins.yaml # from src/jetstream/
templates/ # from src/jetstream/
Procfile # "web: ./jetstream"
manifest.yml # CF manifest (binary_buildpack)

release github (per platform)

stratos-{VERSION}-{os}-{arch}.tar.gz (or .zip for Windows)
bin/jetstream # platform binary
ui/ # frontend assets
config/config.example # configuration template
deploy/containers/ # Docker configs
deploy/kubernetes/ # K8s configs
LICENSE, README.md, CHANGELOG.md, VERSION, README.txt

Plus: stratos-{VERSION}-src.tar.gz via git archive

Known Issues

ENCRYPTION_KEY required

ENCRYPTION_KEY must be explicitly set as an environment variable or in config.properties. The old source-buildpack approach defaulted this via the build script, but the pre-built binary approach does not generate a default. If not set, jetstream fails to start with an encryption error.

For CF deployments, set it in the manifest or via cf set-env:

cf set-env console ENCRYPTION_KEY "$(openssl rand -hex 32)"

Runtime tuning — CAPI pagination

The native CF v3 list endpoints (/pp/v1/cf/orgs/{cnsi}, /pp/v1/cf/apps/{cnsi}, /pp/v1/cf/spaces/{cnsi}) drain every page of the CAPI list API to return the full set. Two environment variables tune how jetstream paginates against CAPI, in case your foundation's CAPI performance differs from the defaults.

STRATOS_CF_PER_PAGE

  • Default: 500
  • Effect: page size (per_page=<n>) used when fetching every page of a CAPI list endpoint.
  • Why override: Larger values reduce round-trip count; smaller values complete each request faster. On adepttech, /v3/spaces?per_page=5000 clocked ~27s/request (just under the 30s CAPI client timeout); per_page=500 completes in ~6s. A faster foundation may tolerate larger pages; a slower one may need smaller.

STRATOS_CF_MAX_PARALLEL_PAGES

  • Default: 5
  • Effect: maximum concurrent CAPI page requests issued by the drain-all helpers. Page 1 is fetched synchronously to learn total_pages; pages 2..N are then fetched with this fan-out bound.
  • Why override: Raise for faster total drain when CAPI tolerates more concurrent requests; lower if concurrent pressure stresses CAPI or the gorouter connection pool.

Applying changes

Both variables are read at jetstream startup, from plain OS environment variables — any of the usual ways to set one works, not just cf set-env:

CF deployment, imperative (change now, on a running app):

cf set-env console STRATOS_CF_PER_PAGE 1000
cf set-env console STRATOS_CF_MAX_PARALLEL_PAGES 8
cf restage console
info

cf restart preserves the environment variable set loaded when the droplet was built and will NOT pick up cf set-env changes. Use cf restage.

CF deployment, declarative (checked into manifest.yml, applied on the next cf push/cf restage — see the commented examples already in the repo's manifest.yml):

applications:
- name: console
env:
STRATOS_CF_PER_PAGE: 1000
STRATOS_CF_MAX_PARALLEL_PAGES: 8

Local (make dev backend or running the binary directly):

export STRATOS_CF_PER_PAGE=1000
export STRATOS_CF_MAX_PARALLEL_PAGES=8
make dev backend

Measuring the effect

DIAGNOSTICS_ENABLED=true (same env-var mechanism as above, FWT-934) adds an X-Stratos-Wire-Sizes response header to JSON API responses — raw_total/keys/values/structural/resources byte breakdown plus duration_ms for that request (wireSizeMiddleware, middleware_wiresize.go). duration_ms on a CAPI-list-backed endpoint (orgs/apps/spaces) is the actual before/after signal for whether a STRATOS_CF_PER_PAGE/STRATOS_CF_MAX_PARALLEL_PAGES change helped — not just an abstract default to trust.

Note: despite DiagnosticsEnabled's doc-comment in api/structs.go describing "the admin-only /pp/v1/admin/diagnostics endpoint," no such route exists in the codebase — what's actually shipped is this response header on existing endpoints, not a separate admin diagnostics endpoint. Off by default in production; opt in per-deployment (dev/staging) via DIAGNOSTICS_ENABLED.

The resolved values are logged at startup:

INFO STRATOS_CF_PER_PAGE=1000 (overrides default 500)
INFO STRATOS_CF_MAX_PARALLEL_PAGES=8 (overrides default 5)

To confirm what's currently set on a deployed instance:

cf env console | grep -E 'STRATOS_CF_PER_PAGE|STRATOS_CF_MAX_PARALLEL_PAGES'