Multi-Brand Application Delivery
- Reduced 12+ per-brand builds (50-60 min) to 1 build (~8 min) through build-time locale tokenization and runtime brand resolution
- Replaced 650 lines of fragile shell scripts with 1,341 lines of tested Go tooling (86 tests) handling tokenization, brand overlay, and container startup
- Container startup resolves brand identity in ~200ms via Go entrypoint that execs into Nginx with zero runtime overhead
- Wrote 2,346 lines of ELI5 architecture documentation with audience-specific reading paths for frontend devs, DevOps, QA, and security reviewers
- CI/CD workflow shrank from 590 lines to ~118 lines while eliminating long-lived PATs in favor of scoped GitHub App tokens
The Challenge
A white-label healthcare platform supports 4+ brands across multiple environments (dev, staging, production). The old build system compiled brand identity into the Flutter web app at build time using 5 shell scripts (~650 lines of bash) that copied brand assets, merged locale files, and injected configuration via sed/awk text processing. This meant:
- 12+ separate Docker builds (4 brands x 3 environments), each taking 15-20 minutes
- A separate FedRAMP Dockerfile maintained in parallel with its own Go entrypoint
- Secrets baked into images at compile time - changing a support email meant rebuilding every environment
- Environment drift - bugs could exist in staging but not dev because they were different binaries
- Fragile CI/CD - a 590-line GitHub Actions workflow with complex branch-based conditional logic to determine which brand/environment combination to build
Approach & Role
I designed and implemented a complete architectural replacement: a single Docker image that resolves brand identity and deployment configuration at container startup through environment variables. The Flutter app compiles once with placeholder tokens, and a Go entrypoint fills in the blanks in ~200ms before handing off to Nginx.
The core insight: Flutter web compiles to JavaScript, and String.fromEnvironment resolves at compile time - there's no way to read environment variables from a browser at runtime. So we compile with deterministic placeholder tokens, then replace them in the generated JavaScript files before serving. This gives us runtime configurability without losing Flutter's compilation model.
Architecture & Patterns
Build-time tokenization (tokenize-locale, 276 lines of Go):
- Scans all brand locale directories to discover which ARB keys are overridden by any brand
- Replaces those values in the base locale files with deterministic tokens:
L10N_<locale>_<arbFile>_<key>_REPLACE - Validates ICU message format compatibility - brand overrides must preserve the same placeholders in the same order as the base locale (prevents runtime format string crashes)
- Runs during Docker build, before
flutter build web - 15 unit tests covering key discovery, token format, and placeholder validation
Runtime brand resolution (entrypoint, 1,000 lines of Go):
- Copies the Flutter build to a writable working directory (supports read-only root filesystems)
- Overlays the active brand's visual assets (logos, favicons, PWA icons, informed consent docs)
- Resolves locale tokens: loads brand ARB overrides with base fallback, replaces every
L10N_*_REPLACEtoken in the compiled JavaScript - Replaces environment variable tokens (
..._TPL_REPLACE) with actual values from the container environment - Recalculates service worker cache hashes (Flutter's service worker uses MD5 hashes for cache busting - stale hashes after token replacement would serve cached content to returning users)
- Generates per-stack
main.dart.jsvariants for multi-domain blue/green deployments - Verifies no required tokens remain unreplaced - the container refuses to start if a required env var is missing. Never serves a partially-configured app.
- Execs into Nginx via syscall (Go process disappears, Nginx becomes PID 1, zero runtime overhead)
- 66 unit tests covering every replacement path, error condition, and edge case
Healthcheck (healthcheck, 65 lines of Go):
- Reads
BIND_ADDRandBIND_PORTfrom environment automatically - HTTP GET to the health endpoint, reports container status
- 5 unit tests
4-stage Dockerfile:
- Go build - compiles entrypoint, healthcheck, and tokenize-locale to static binaries (CGO_ENABLED=0)
- Flutter build - installs SDK (pinned version + SHA256 verification), tokenizes locales, generates l10n, runs build_runner, then builds with placeholder
--dart-definevalues - App assembly - combines Go binaries, Flutter output, brand assets, base locales, and config into a clean filesystem layout
- Runtime - Chainguard Nginx or customized Azure Linux based FIPS + Nginx, runs as non-root (UID 65532), healthcheck configured
Security considerations:
BRAND_NAMEvalidated for path traversal (no/, no..)REPLACEMENTS_CONFIGrestricted to/app/config/prefix- File-based secrets support under
/run/secrets/or/app/secrets/paths are read from disk (compatible with Docker Swarm, Docker Compose) and Environment Variables (ECS Tasks/Services) for multi-platform compatibility - Read-only root filesystem compatible (all work done in
/tmp), temporary volume mounts for required read/write data - Inline FedRAMP/SOC2 compliance annotations: CM-6 (Configuration Settings), SI-7 (Software Integrity), CC6.1 (Logical Access), CC8.1 (Change Management), etc
CI/CD simplification:
- Old: 590-line workflow with conditional brand/environment matrix, per-brand build jobs, and complex branch-based logic
- New: ~118-line workflow. One build job, one push to container registry, optional dev auto-deploy
- GitHub App tokens (short-lived, scoped) replace long-lived PATs for cross-repo access
- Concurrency control: one build per branch at a time, in-progress builds cancelled on new push
- Smoke test: container started with test env vars, validated that startup succeeds and tokens resolve
Impact & Scale
- 12+ builds (50-60 min) collapsed to 1 build (~8 min)
- Any config/brand changes requiring full rebuild now requires a container restart
- 650 lines of untested and fragile shell scripts and a parallel FedRAMP Dockerfile eliminated entirely
- 1,341 lines of Go tooling with 86 tests replacing fragile bash text processing
- 2,346 lines of ELI5 documentation written for the team (8 staged architecture guides + onboarding recipe + troubleshooting catalog)
- Container startup completes in ~200ms (copy: 50ms, overlay: 20ms, locale resolution: 30ms, env replacement: 80ms, verification: 20ms)
- Zero application code changes required - the Flutter app has no awareness of the multi-brand system
- Dev, staging, and production serve the exact same binary - only environment variables differ
- Brand configuration changes deploy independently from application code