Agent-readable wiki

dk-flags Developer Reference Wiki

dk-flags is a Docker image factory that ships a fully pre-seeded FeatureHub party-server for Dark Kitchen local development and integration testing. The image uses a modular Node.js pipeline executed at build time to initialize admin credentials, portfolios, and typed feature flags via the FeatureHub Management REST API — so containers start ready to use with no post-start setup.

Pages

  1. Technical OrientationWhat dk-flags is, its core purpose, the build-time initialization strategy, the module layout, and how the rest of this reference is organized. Entry point for any engineer onboarding to the repo.
  2. Image Build & Runtime LifecycleHow the Dockerfile extends featurehub/party-server:latest, how build-setup.sh temporarily starts and stops the embedded party-server during docker build to run the setup pipeline, and how entrypoint.sh launches the server at runtime with no re-initialization. Covers the build-time vs run-time env var contract and the server discovery fallback chain.
  3. HTTP Client, Readiness Probe & AuthenticationThe api.js module: makeRequest() signature, Bearer token injection, JSON vs text response handling, and the waitForApp() readiness loop (60 s MAX_WAIT_TIME). The auth.js module: the two-phase authenticate() flow — initializeAdmin() via POST /mr-api/initialize, falling back to login() via POST /mr-api/login when the system is already bootstrapped. Token extraction and propagation to downstream calls.
  4. Portfolio, Application & Feature Flag Provisioningportfolio.js: getPortfolio() (selects the first portfolio from GET /mr-api/portfolio), createApplication() (POST to portfolio endpoint), getEnvironment() (resolves default environment). features.js: the createAllFeatures() orchestration loop — createFeature() (idempotent, 409 treated as skipped), unlockBooleanFlag() (BOOLEAN-only unlock step via PUT feature-environments), setFeatureValue() (typed value dispatch: valueBoolean / valueString / valueNumber / valueJson), and the created/skipped/errors summary. flags.js: loadFlags() 4-path search strategy.
  5. Flag Definition Schema & Supported TypesThe exampleFlags.json contract: required fields (key, type, defaultValue), optional fields (name, description), and the four supported type values (BOOLEAN, STRING, NUMBER, JSON). How each type maps to a typed value field in the FeatureHub API (valueBoolean, valueString, valueNumber, valueJson). The special BOOLEAN unlock step. How to add, remove, or change seed flags and rebuild. The seven seed flags shipped by default.
  6. Build, Deploy & Extension PointsHow to build (docker build -f docker/Dockerfile) and run (docker run -p 8085:8085) the image with default and overridden env vars. The CI/CD pipeline: dkcicd build-and-push-image-v1 on push to main, publishing to AWS ECR account 053131491888 / us-east-1 / dk-flags-party-server:0.0.1. Production endpoint https://flags.vtex.com/. Backstage catalog registration (te-0034 / OG35ZXTZ). Downstream OpenFeature SDK consumers. Known risks: floating base image tag, mutable 0.0.1 ECR tag, build-time-only admin baking. Outstanding follow-ups from the spec: reconcile package.json#main, make portfolio/org/app names env-overridable, pin the base image.

Complete Markdown

# dk-flags Developer Reference Wiki

> dk-flags is a Docker image factory that ships a fully pre-seeded FeatureHub party-server for Dark Kitchen local development and integration testing. The image uses a modular Node.js pipeline executed at build time to initialize admin credentials, portfolios, and typed feature flags via the FeatureHub Management REST API — so containers start ready to use with no post-start setup.

## Context Links

- [Agent index](https://grok-wiki.com/public/wiki/vtex-dk-flags-0a8c140c3cfa/llms.txt)
- [Human interactive wiki](https://grok-wiki.com/public/wiki/vtex-dk-flags-0a8c140c3cfa)
- [GitHub repository](https://github.com/vtex/dk-flags)

## Repository Metadata

- Repository: vtex/dk-flags

- Generated: 2026-05-26T16:12:12.802Z
- Updated: 2026-05-26T16:22:04.251Z
- Runtime: Claude Code
- Format: Technical
- Pages: 6

## Page Index

- 01. [Technical Orientation](https://grok-wiki.com/public/wiki/vtex-dk-flags-0a8c140c3cfa/pages/01-technical-orientation.md) - What dk-flags is, its core purpose, the build-time initialization strategy, the module layout, and how the rest of this reference is organized. Entry point for any engineer onboarding to the repo.
- 02. [Image Build & Runtime Lifecycle](https://grok-wiki.com/public/wiki/vtex-dk-flags-0a8c140c3cfa/pages/02-image-build-runtime-lifecycle.md) - How the Dockerfile extends featurehub/party-server:latest, how build-setup.sh temporarily starts and stops the embedded party-server during docker build to run the setup pipeline, and how entrypoint.sh launches the server at runtime with no re-initialization. Covers the build-time vs run-time env var contract and the server discovery fallback chain.
- 03. [HTTP Client, Readiness Probe & Authentication](https://grok-wiki.com/public/wiki/vtex-dk-flags-0a8c140c3cfa/pages/03-http-client-readiness-probe-authentication.md) - The api.js module: makeRequest() signature, Bearer token injection, JSON vs text response handling, and the waitForApp() readiness loop (60 s MAX_WAIT_TIME). The auth.js module: the two-phase authenticate() flow — initializeAdmin() via POST /mr-api/initialize, falling back to login() via POST /mr-api/login when the system is already bootstrapped. Token extraction and propagation to downstream calls.
- 04. [Portfolio, Application & Feature Flag Provisioning](https://grok-wiki.com/public/wiki/vtex-dk-flags-0a8c140c3cfa/pages/04-portfolio-application-feature-flag-provisioning.md) - portfolio.js: getPortfolio() (selects the first portfolio from GET /mr-api/portfolio), createApplication() (POST to portfolio endpoint), getEnvironment() (resolves default environment). features.js: the createAllFeatures() orchestration loop — createFeature() (idempotent, 409 treated as skipped), unlockBooleanFlag() (BOOLEAN-only unlock step via PUT feature-environments), setFeatureValue() (typed value dispatch: valueBoolean / valueString / valueNumber / valueJson), and the created/skipped/errors summary. flags.js: loadFlags() 4-path search strategy.
- 05. [Flag Definition Schema & Supported Types](https://grok-wiki.com/public/wiki/vtex-dk-flags-0a8c140c3cfa/pages/05-flag-definition-schema-supported-types.md) - The exampleFlags.json contract: required fields (key, type, defaultValue), optional fields (name, description), and the four supported type values (BOOLEAN, STRING, NUMBER, JSON). How each type maps to a typed value field in the FeatureHub API (valueBoolean, valueString, valueNumber, valueJson). The special BOOLEAN unlock step. How to add, remove, or change seed flags and rebuild. The seven seed flags shipped by default.
- 06. [Build, Deploy & Extension Points](https://grok-wiki.com/public/wiki/vtex-dk-flags-0a8c140c3cfa/pages/06-build-deploy-extension-points.md) - How to build (docker build -f docker/Dockerfile) and run (docker run -p 8085:8085) the image with default and overridden env vars. The CI/CD pipeline: dkcicd build-and-push-image-v1 on push to main, publishing to AWS ECR account 053131491888 / us-east-1 / dk-flags-party-server:0.0.1. Production endpoint https://flags.vtex.com/. Backstage catalog registration (te-0034 / OG35ZXTZ). Downstream OpenFeature SDK consumers. Known risks: floating base image tag, mutable 0.0.1 ECR tag, build-time-only admin baking. Outstanding follow-ups from the spec: reconcile package.json#main, make portfolio/org/app names env-overridable, pin the base image.

## Source File Index

- `.vtex/catalog-info.yaml`
- `.vtex/deployment.yaml`
- `docker/api.js`
- `docker/auth.js`
- `docker/build-setup.sh`
- `docker/config.js`
- `docker/Dockerfile`
- `docker/entrypoint.sh`
- `docker/exampleFlags.json`
- `docker/features.js`
- `docker/flags.js`
- `docker/package.json`
- `docker/portfolio.js`
- `docker/run-setup.sh`
- `docker/setup.js`
- `README.md`
- `specs/dk-flags-party-server-image.md`

---

## 01. Technical Orientation

> What dk-flags is, its core purpose, the build-time initialization strategy, the module layout, and how the rest of this reference is organized. Entry point for any engineer onboarding to the repo.

- Page Markdown: https://grok-wiki.com/public/wiki/vtex-dk-flags-0a8c140c3cfa/pages/01-technical-orientation.md
- Generated: 2026-05-26T16:10:26.558Z

### Source Files

- `README.md`
- `docker/Dockerfile`
- `docker/setup.js`
- `docker/config.js`
- `docker/exampleFlags.json`
- `.vtex/catalog-info.yaml`
- `specs/dk-flags-party-server-image.md`

<details>
<summary>Relevant source files</summary>
The following files were used as context for generating this wiki page:

- [README.md](README.md)
- [docker/Dockerfile](docker/Dockerfile)
- [docker/build-setup.sh](docker/build-setup.sh)
- [docker/entrypoint.sh](docker/entrypoint.sh)
- [docker/setup.js](docker/setup.js)
- [docker/config.js](docker/config.js)
- [docker/api.js](docker/api.js)
- [docker/auth.js](docker/auth.js)
- [docker/portfolio.js](docker/portfolio.js)
- [docker/features.js](docker/features.js)
- [docker/flags.js](docker/flags.js)
- [docker/exampleFlags.json](docker/exampleFlags.json)
- [.vtex/catalog-info.yaml](.vtex/catalog-info.yaml)
- [specs/dk-flags-party-server-image.md](specs/dk-flags-party-server-image.md)
</details>

# Technical Orientation

`dk-flags` is the VTEX Dark Kitchen repository that ships a self-contained Docker image (`dk-flags-party-server`) wrapping [FeatureHub](https://docs.featurehub.io/featurehub/latest/index.html)'s `party-server`. Its defining design decision is that the entire initialization pipeline — admin user bootstrap, portfolio/application/environment creation, and feature flag seeding — runs **at image build time**, not at container start. The result is a container that is ready to serve immediately upon `docker run` with no extra sidecars, init containers, or post-start scripts.

This page explains what the repository contains, why the build-time approach was chosen, how the Node.js setup modules are organized, and how the image is registered and published. It is the recommended starting point for any engineer integrating with or maintaining the DK flags infrastructure.

---

## What dk-flags Is

DK engineers need a reproducible, pre-seeded FeatureHub instance for local development and integration testing. The production FeatureHub deployment is hosted at **https://flags.vtex.com/**, but every local machine and CI job previously required a multi-step manual bootstrap. `dk-flags` solves this by versioning all initialization logic in one repository and baking the resulting state into a Docker image.

The primary artifact is the Docker image. Everything in the repository supports building, populating, and publishing that image. The repository does **not** own the OpenFeature SDK clients (those live in separate repositories listed in the README), nor the production deployment topology behind `flags.vtex.com`.

Sources: [README.md:1-13](), [specs/dk-flags-party-server-image.md:9-19]()

---

## Build-Time Initialization Strategy

The central architectural decision is: **initialize once at `docker build` time, ship the state as an image layer, and let `docker run` find everything already in place**.

```text
┌─────────────────────────────────────────────────────────────┐
│  docker build                                               │
│                                                             │
│  1. FROM featurehub/party-server:latest                     │
│  2. apk add nodejs npm curl procps                          │
│  3. COPY docker/*.js + exampleFlags.json → /app/            │
│  4. RUN /app/build-setup.sh                                 │
│       ├── start party-server (bathe.BatheBooter) → bg      │
│       ├── poll APP_URL up to 120 s for HTTP 200             │
│       ├── node /app/setup.js  (full API bootstrap)          │
│       └── kill server (graceful → SIGKILL after ~2 s)       │
│  5. EXPOSE 8085                                             │
│  6. ENTRYPOINT ["/app/entrypoint.sh"]                       │
└─────────────────────────────────────────────────────────────┘
         │
         ▼ image layer contains post-init FeatureHub state
┌─────────────────────────────────────────────────────────────┐
│  docker run                                                 │
│                                                             │
│  entrypoint.sh → exec party-server (foreground, no setup)   │
│  Container ready immediately on :8085                       │
└─────────────────────────────────────────────────────────────┘
```

The build-time server is launched using `bathe.BatheBooter` (the launcher embedded in the base image's classpath) and logs to `/tmp/server.log`. `build-setup.sh` polls `$APP_URL` with `curl` every 2 seconds for up to 120 seconds, then hands off to `node /app/setup.js`. The server is stopped — gracefully first, then `kill -9` after ~2 seconds — before the image layer is committed.

At run time, `entrypoint.sh` simply `exec`s the same server command; no initialization re-runs. Environment variables such as `ADMIN_USER` or `ADMIN_EMAIL` override admin credentials only when passed at **build time**. Passing them at `docker run` time has no effect on the already-embedded credentials.

Sources: [docker/Dockerfile:5-54](), [docker/build-setup.sh:6-128](), [docker/entrypoint.sh:1-44](), [specs/dk-flags-party-server-image.md:181-191]()

### Why Not Initialize at Container Start?

| Alternative | Rejection reason |
|---|---|
| Init at container start (entrypoint runs setup) | Every start takes seconds; race-prone on cold start |
| Sidecar / one-shot Kubernetes Job | Consumers must coordinate the job; couples seed lifecycle to deploys |
| FeatureHub CLI / setup.yaml | CLI adds image weight; REST API gives full typed-value control with plain `node` + `fetch` |
| Headless browser scripting the Admin UI | Heavy (Chromium); brittle to UI changes |
| External volume seeded at runtime | Loses self-contained image guarantee |

Sources: [specs/dk-flags-party-server-image.md:158-165]()

---

## Module Layout

All initialization code lives under `docker/`. The Node.js pipeline is split into seven focused modules, plus three shell scripts and one data file.

### Node.js Modules

```text
docker/
├── config.js       ← environment / constant values
├── api.js          ← HTTP client + readiness probe
├── auth.js         ← admin bootstrap + login
├── portfolio.js    ← portfolio / app / environment resolution
├── features.js     ← create, unlock, and value-set per flag
├── flags.js        ← load exampleFlags.json from disk
└── setup.js        ← orchestrator (entry point)
```

#### `config.js`

Exports all runtime constants from environment variables with hard-coded defaults:

```js
// docker/config.js
module.exports = {
    APP_URL: process.env.APP_URL || 'http://localhost:8085',
    ADMIN_USER: process.env.ADMIN_USER || 'admin',
    ADMIN_PASSWORD: process.env.ADMIN_PASSWORD || 'admin',
    ADMIN_EMAIL: process.env.ADMIN_EMAIL || 'admin@example.com',
    MAX_WAIT_TIME: 60000,
    PORTFOLIO_NAME: 'integrationTest',
    ORGANIZATION_NAME: 'vtex',
    APPLICATION_NAME: 'integrationTest'
};
```

`PORTFOLIO_NAME`, `ORGANIZATION_NAME`, and `APPLICATION_NAME` are hard-coded (not env-overridable) in the current version. Sources: [docker/config.js:1-10]()

#### `api.js`

Provides two exports: `makeRequest(method, path, data?, token?)` for all FeatureHub Management REST calls (using Node 18+ built-in `fetch`), and `waitForApp()` which polls `APP_URL` until the root returns `ok` or `MAX_WAIT_TIME` is exceeded.

#### `auth.js`

`authenticate()` first attempts `POST /mr-api/initialize` (first-time site admin creation). If that fails (the system is already initialized), it falls back to `POST /mr-api/login` with the configured credentials. Returns a bearer token or `null`. Sources: [docker/auth.js:4-84]()

#### `portfolio.js`

Resolves the FeatureHub object graph:
- `getPortfolio(token)` → `GET /mr-api/portfolio`; returns the ID of the first portfolio found.
- `createApplication(portfolioId, token)` → `POST /mr-api/portfolio/{id}/application`.
- `getEnvironment(applicationId, token)` → `GET /mr-api/application/{id}/environment`; returns the first environment ID.

#### `features.js`

Implements three low-level operations and one high-level orchestrator:

| Function | REST call | Notes |
|---|---|---|
| `createFeature(appId, flag, token)` | `POST /mr-api/application/{id}/features` | Returns `{ success, skipped }` (HTTP 409 → `skipped: true`) |
| `unlockBooleanFlag(appId, key, envId, value, version, token)` | `PUT /mr-api/application/{id}/feature-environments/{key}` | Only called for `BOOLEAN` flags; sets `locked: false` + `valueBoolean` |
| `setFeatureValue(envId, flag, version, token)` | `PUT /mr-api/features/{envId}/feature/{key}` | Sets the single typed field (`valueBoolean`, `valueString`, `valueNumber`, or `valueJson`) |
| `createAllFeatures(appId, envId, flags, token)` | — | Iterates flags, calls the above three in order, tracks created/skipped/error counters |

The `BOOLEAN` flag requires an explicit unlock call **before** the value-set call because FeatureHub locks new flags by default; non-boolean types set `locked: false` directly in the value-set body. Sources: [docker/features.js:39-64](), [specs/dk-flags-party-server-image.md:213-218]()

#### `flags.js`

`loadFlags()` searches four candidate file paths in priority order (`__dirname`, `cwd`, `/app/`, `./`) for `exampleFlags.json`. Returns the parsed array or `null` (with a warning) if not found. Sources: [docker/flags.js:4-29]()

#### `setup.js`

The orchestrator. Calls the five steps in order and exits 0 on success or 1 on a thrown error. Mid-pipeline failures (e.g., `portfolioId` is `null`) emit a warning and return without throwing, so the build still succeeds but with partial state. Sources: [docker/setup.js:8-82]()

### Shell Scripts

| Script | Invoked by | Purpose |
|---|---|---|
| `docker/build-setup.sh` | `Dockerfile` (`RUN`) | Start server → wait → run `setup.js` → stop server |
| `docker/entrypoint.sh` | `ENTRYPOINT` | Start server in foreground at container run time (no setup) |
| `docker/run-setup.sh` | Manual only | Poll readiness, then run `setup.js` against a live server; not used by the image build |

### Seed Data

`docker/exampleFlags.json` defines the seven flags baked into every image build:

| Key | Type | Default Value |
|---|---|---|
| `booleanConf` | `BOOLEAN` | `true` |
| `stringConf` | `STRING` | `"myStringConf"` |
| `intConf` | `NUMBER` | `10` |
| `floatConf` | `NUMBER` | `1.5` |
| `jsonConf` | `JSON` | `{ "data": "test", "evaluation": true }` |
| `objectConf` | `JSON` | nested object with string/int/bool/array attributes |
| `stringArrayConf` | `JSON` | `["stringValue1", "stringValue2", "stringValue3"]` |

All flags are created under the portfolio `integrationTest`, application `integrationTest`, and the first discovered environment. Sources: [docker/exampleFlags.json:1-55](), [docker/config.js:8-9]()

---

## API Endpoints Consumed at Build Time

`setup.js` interacts exclusively with the FeatureHub Management REST API (prefix: `$APP_URL/mr-api`):

| Method | Path | Purpose |
|---|---|---|
| `GET` | `/` | Readiness probe |
| `POST` | `/mr-api/initialize` | Create site admin (first-time) |
| `POST` | `/mr-api/login` | Auth fallback if already initialized |
| `GET` | `/mr-api/portfolio` | Discover portfolio |
| `POST` | `/mr-api/portfolio/{portfolioId}/application` | Create application |
| `GET` | `/mr-api/application/{applicationId}/environment` | Resolve environment |
| `POST` | `/mr-api/application/{applicationId}/features` | Create feature definition |
| `PUT` | `/mr-api/application/{applicationId}/feature-environments/{key}` | Unlock BOOLEAN flag |
| `PUT` | `/mr-api/features/{environmentId}/feature/{key}` | Set typed value |

Sources: [specs/dk-flags-party-server-image.md:288-298]()

---

## Environment Variables

| Variable | Default | Scope | Effect |
|---|---|---|---|
| `APP_URL` | `http://localhost:8085` | Build + Run | URL polled by `build-setup.sh` and `config.js` |
| `ADMIN_USER` | `admin` | **Build only** | Admin display name baked into the image |
| `ADMIN_PASSWORD` | `admin` | **Build only** | Admin password baked into the image |
| `ADMIN_EMAIL` | `admin@example.com` | **Build only** | Admin login email baked into the image |

The defaults are intentionally insecure and documented as dev/test-only. Production builds must override all `ADMIN_*` variables at **build time** so that real credentials are never stored in the default image. Sources: [docker/Dockerfile:39-43](), [specs/dk-flags-party-server-image.md:170-171]()

---

## Catalog Registration and CI/CD

The component is registered in the VTEX Backstage catalog under `.vtex/catalog-info.yaml`:

- **Name**: `dk-flags`
- **Owner**: `te-0034`
- **Application ID**: `OG35ZXTZ`
- **Lifecycle**: `stable`
- **Type**: `fullstack-app`

On every push to the `main` branch, the `dkcicd` pipeline (`build-and-push-image-v1`) builds the image on `amd64` and pushes it to AWS ECR (account `053131491888`, region `us-east-1`, repo `dk-flags-party-server`, tag `0.0.1`). The `0.0.1` tag is mutable; consumers who need pinned versions should reference the image by digest.

Sources: [.vtex/catalog-info.yaml:1-22](), [specs/dk-flags-party-server-image.md:219-224]()

---

## How the Reference Is Organized

| Page area | What it covers |
|---|---|
| **Technical Orientation** (this page) | Repository purpose, build-time init strategy, module layout, CI/CD |
| **Docker Setup** | Detailed build and run instructions, env var reference (see README) |
| **Flag Schema & Seeding** | `exampleFlags.json` format, supported types, default values, how to add flags |
| **FeatureHub Management API** | Endpoint contracts consumed by `setup.js` |
| **OpenFeature SDK Clients** | Frontend and backend SDKs (separate repositories; linked from README) |

---

The repository is intentionally narrow in scope: it owns the image build pipeline and the seed flag definitions. Persistence across restarts, production deployment topology (load balancer, TLS, secrets rotation, scaling), and OpenFeature SDK distribution are all handled outside this repository. Any engineer extending the image should start by editing `docker/exampleFlags.json` and rebuilding — the modular setup pipeline will handle the rest automatically at build time.

Sources: [specs/dk-flags-party-server-image.md:89-98]()

---

## 02. Image Build & Runtime Lifecycle

> How the Dockerfile extends featurehub/party-server:latest, how build-setup.sh temporarily starts and stops the embedded party-server during docker build to run the setup pipeline, and how entrypoint.sh launches the server at runtime with no re-initialization. Covers the build-time vs run-time env var contract and the server discovery fallback chain.

- Page Markdown: https://grok-wiki.com/public/wiki/vtex-dk-flags-0a8c140c3cfa/pages/02-image-build-runtime-lifecycle.md
- Generated: 2026-05-26T16:10:14.526Z

### Source Files

- `docker/Dockerfile`
- `docker/build-setup.sh`
- `docker/entrypoint.sh`
- `docker/run-setup.sh`
- `docker/package.json`
- `specs/dk-flags-party-server-image.md`

<details>
<summary>Relevant source files</summary>
The following files were used as context for generating this wiki page:

- [docker/Dockerfile](docker/Dockerfile)
- [docker/build-setup.sh](docker/build-setup.sh)
- [docker/entrypoint.sh](docker/entrypoint.sh)
- [docker/run-setup.sh](docker/run-setup.sh)
- [docker/package.json](docker/package.json)
- [docker/config.js](docker/config.js)
- [docker/setup.js](docker/setup.js)
- [specs/dk-flags-party-server-image.md](specs/dk-flags-party-server-image.md)
</details>

# Image Build & Runtime Lifecycle

The `dk-flags-party-server` Docker image extends `featurehub/party-server:latest` and adds a one-time initialization pipeline that runs **at image build time**. By baking the FeatureHub admin user, portfolio, application, and seed feature flags into an image layer during `docker build`, every container that starts from the image is immediately ready to serve requests — no startup hooks, no sidecar containers, no manual bootstrapping.

This page explains how the build-time and run-time phases divide responsibilities, how each shell script discovers and controls the embedded server, and what the environment variable contract means for operators who need to customize credentials or the server URL.

---

## Phase Separation: Build vs. Runtime

The image lifecycle is split into two fully independent phases:

```text
┌──────────────────────────────────────────────────────────────┐
│  docker build                                                 │
│                                                               │
│  1. Extend featurehub/party-server:latest                     │
│  2. apk add nodejs npm curl procps                            │
│  3. COPY docker/*.js + exampleFlags.json → /app/             │
│  4. npm install (zero dependencies; engines: node >=18)       │
│  5. RUN build-setup.sh                                        │
│       ├─ start party-server (background)                      │
│       ├─ wait up to 120 s for GET $APP_URL → 200             │
│       ├─ node /app/setup.js  (5-step init pipeline)           │
│       └─ kill party-server; finalize layer                    │
│                                                               │
│  Result: image layer contains fully-initialized H2 state      │
└──────────────────────────┬───────────────────────────────────┘
                           │ (image artifact)
┌──────────────────────────▼───────────────────────────────────┐
│  docker run                                                   │
│                                                               │
│  ENTRYPOINT /app/entrypoint.sh                                │
│       └─ exec party-server (foreground, no setup re-run)      │
│                                                               │
│  Container is ready when the server is ready.                 │
│  No initialization happens; no extra wait.                    │
└──────────────────────────────────────────────────────────────┘
```

The build-time server and run-time server are the **same binary** launched by the same discovery logic, but play different roles: the build-time instance is ephemeral (started in the background, stopped after setup), while the run-time instance is the container's process 1.

Sources: [docker/Dockerfile:44-53](), [docker/build-setup.sh:1-127](), [docker/entrypoint.sh:39-43]()

---

## Base Image and System Dependencies

```dockerfile
FROM featurehub/party-server:latest

RUN set -eux; \
    apk add --no-cache \
        nodejs \
        npm \
        curl \
        procps;
```

The base image is Alpine-based and ships `bathe.BatheBooter` plus the FeatureHub party-server JARs pre-installed under `/app/classpath/` and `/app/libs/`. The `RUN apk add` layer adds:

| Package | Purpose |
|---------|---------|
| `nodejs` / `npm` | Run the Node.js 18+ setup pipeline (`setup.js` and its modules) |
| `curl` | HTTP readiness probe in `build-setup.sh` and `run-setup.sh` |
| `procps` | Provides `kill -0` (process liveness test) used in `build-setup.sh` |

`package.json` declares `engines: { "node": ">=18.0.0" }` and lists **zero npm dependencies** — the setup pipeline uses Node's built-in `fetch` and CommonJS modules only.

Sources: [docker/Dockerfile:1-13](), [docker/package.json:8-9]()

---

## Build-Time: `build-setup.sh`

`build-setup.sh` is executed as a `RUN` layer in the Dockerfile. It orchestrates the temporary server lifecycle around the setup pipeline.

### Server Discovery Fallback Chain

Because the base image layout can vary across `featurehub/party-server` versions, the script tries four strategies in order:

```text
1. /app/classpath/party-server*.jar exists?
   → java -cp "/app/classpath/*:/app/libs/*" bathe.BatheBooter
         -Rio.featurehub.party.Application
         -P/etc/common-config/common.properties
         -P/etc/app-config/application.properties
         -P/etc/app-config/secrets.properties  (background &)

2. Any *.jar in /app (excluding libs/)?
   → java -jar <first-found>.jar  (background &)

3. featurehub-party-server on $PATH?
   → featurehub-party-server  (background &)

4. Executable file at one of:
     /app/party-server
     /usr/local/bin/party-server
     /opt/party-server/party-server
   → exec from that path  (background &)

5. None found → "Error: Could not find server executable" → exit 1
```

Strategy 1 (`bathe.BatheBooter`) is the primary path for the current base image. All strategies capture `stdout`/`stderr` to `/tmp/server.log`.

Sources: [docker/build-setup.sh:13-73]()

### Readiness Wait

After the server starts in the background, the script polls `$APP_URL` (default `http://localhost:8085`) with `curl -f -s` every 2 seconds, up to `MAX_WAIT=120` seconds. If the server does not respond within that window, the script prints the last 50 lines of `/tmp/server.log`, kills the server process, and exits 1 — failing the `docker build`.

```sh
while [ $WAIT_COUNT -lt $MAX_WAIT ]; do
  if curl -f -s "$APP_URL" > /dev/null 2>&1; then
    echo "✓ Server is ready!"
    break
  fi
  sleep 2
  WAIT_COUNT=$((WAIT_COUNT + 2))
done
```

Sources: [docker/build-setup.sh:82-102]()

### Running the Setup Pipeline

Once the server is healthy, the script hands off to the Node.js setup orchestrator:

```sh
if node /app/setup.js; then
  echo "✓ Setup completed successfully!"
else
  kill $SERVER_PID 2>/dev/null || true
  exit 1
fi
```

A non-zero exit from `setup.js` causes the script to kill the server and fail the build.

Sources: [docker/build-setup.sh:104-113]()

### Graceful Server Shutdown

After successful setup, the server is stopped so it does not persist as a running process inside the committed image layer:

```sh
kill $SERVER_PID 2>/dev/null || true
sleep 2
if kill -0 $SERVER_PID 2>/dev/null; then
  kill -9 $SERVER_PID 2>/dev/null || true
fi
wait $SERVER_PID 2>/dev/null || true
```

The graceful `SIGTERM` is followed by a 2-second wait; if the process is still alive, `SIGKILL` is sent. This ensures the image layer captures a clean stopped state.

Sources: [docker/build-setup.sh:115-127]()

---

## Node.js Setup Pipeline (`setup.js`)

The setup pipeline is a modular Node.js orchestrator invoked by `build-setup.sh`. It is composed of six CommonJS modules under `docker/`:

```text
docker/
├── setup.js        ← orchestrator (5 ordered steps)
├── config.js       ← env var + hard-coded constants
├── api.js          ← HTTP helpers + readiness probe
├── auth.js         ← initialize or login, returns bearer token
├── portfolio.js    ← portfolio / application / environment discovery
├── features.js     ← create, unlock, and set-value per flag
└── flags.js        ← load exampleFlags.json from candidate paths
```

### Orchestration Steps

```mermaid
sequenceDiagram
    participant B as build-setup.sh
    participant S as party-server (build-time, background)
    participant O as setup.js
    participant API as FeatureHub Mgmt API

    B->>S: start (bathe.BatheBooter or fallback)
    B->>S: poll GET $APP_URL every 2s (max 120s)
    B->>O: node /app/setup.js
    O->>API: waitForApp() — re-checks readiness (max 60s)
    O->>API: POST /mr-api/initialize  (create site admin)
    alt already initialized
        O->>API: POST /mr-api/login  (fallback)
    end
    API-->>O: bearer token
    O->>API: GET /mr-api/portfolio  (select first)
    O->>API: POST /mr-api/portfolio/{id}/application  (name: integrationTest)
    O->>API: GET /mr-api/application/{id}/environment  (select first)
    loop each flag in exampleFlags.json
        O->>API: POST /mr-api/application/{id}/features
        alt type == BOOLEAN
            O->>API: PUT /mr-api/application/{id}/feature-environments/{key}  (unlock)
        end
        O->>API: PUT /mr-api/features/{envId}/feature/{key}  (set typed value)
    end
    O-->>B: exit 0
    B->>S: kill (SIGTERM → SIGKILL after 2s)
```

`setup.js` exits 0 on success (including the "no flags" case when `exampleFlags.json` is missing), and exits 1 only when an unhandled error is thrown from the orchestrator.

Sources: [docker/setup.js:1-82]()

### Configuration (`config.js`)

All environment-injectable values and hard-coded constants are centralized in `config.js`:

```js
module.exports = {
    APP_URL:           process.env.APP_URL           || 'http://localhost:8085',
    ADMIN_USER:        process.env.ADMIN_USER         || 'admin',
    ADMIN_PASSWORD:    process.env.ADMIN_PASSWORD     || 'admin',
    ADMIN_EMAIL:       process.env.ADMIN_EMAIL        || 'admin@example.com',
    MAX_WAIT_TIME:     60000, // 60 s (not env-overridable)
    PORTFOLIO_NAME:    'integrationTest',
    ORGANIZATION_NAME: 'vtex',
    APPLICATION_NAME:  'integrationTest',
};
```

`PORTFOLIO_NAME`, `ORGANIZATION_NAME`, and `APPLICATION_NAME` are **not env-driven** — they are hard-coded and require a code change to rename.

Sources: [docker/config.js:1-10]()

---

## Runtime: `entrypoint.sh`

At `docker run` time, the entrypoint starts the server in the foreground without repeating any initialization:

```sh
# Setup was already done during build, so just start the server
echo "Starting FeatureHub party-server (setup already completed during build)..."
start_server "$@"
```

### Runtime Discovery Fallback Chain

`entrypoint.sh` uses the same binary discovery order as `build-setup.sh`, but critically uses `exec` to replace the shell process, making the server PID 1:

```text
1. /app/libs/bathe-booter*.jar AND /app/classpath/party-server*.jar both exist?
   → exec java -cp "/app/classpath/*:/app/libs/*" bathe.BatheBooter
         -Rio.featurehub.party.Application
         -P/etc/common-config/common.properties
         -P/etc/app-config/application.properties
         -P/etc/app-config/secrets.properties

2. featurehub-party-server on $PATH?
   → exec featurehub-party-server

3. /app/party-server?
   → exec /app/party-server

4. /usr/local/bin/party-server?
   → exec /usr/local/bin/party-server

5. None found → exit 1
```

If the `ENTRYPOINT` is called with arguments (e.g., a `CMD` override), `exec "$@"` is used instead, delegating to the base-image default command.

Sources: [docker/entrypoint.sh:5-36]()

### Key Difference from Build-Time Startup

| Aspect | `build-setup.sh` (build) | `entrypoint.sh` (runtime) |
|--------|--------------------------|--------------------------|
| Background vs foreground | Background (`&`) | Foreground (`exec`) |
| PID tracking | `$SERVER_PID` (to kill later) | PID 1 (container process) |
| Log capture | `> /tmp/server.log 2>&1` | Direct stdout/stderr |
| Setup invocation | Yes — runs `node setup.js` | No — setup already baked |
| Shutdown | Script-controlled after setup | Container stop signal |

---

## Environment Variable Contract

### Build-Time Variables

These are the only variables that affect the embedded state baked into the image layer. They must be set **before** or **during** `docker build`:

| Variable | Default | Effect |
|----------|---------|--------|
| `APP_URL` | `http://localhost:8085` | URL that `build-setup.sh` polls for readiness and that `setup.js` uses for all API calls |
| `ADMIN_USER` | `admin` | Display name of the site admin created by `POST /mr-api/initialize` |
| `ADMIN_PASSWORD` | `admin` | Password for the site admin |
| `ADMIN_EMAIL` | `admin@example.com` | Email (login identifier) for the site admin |

The Dockerfile sets these as `ENV` instructions, making them available to the `RUN build-setup.sh` layer:

```dockerfile
ENV APP_URL=http://localhost:8085
ENV ADMIN_USER=admin
ENV ADMIN_PASSWORD=admin
ENV ADMIN_EMAIL=admin@example.com
```

Sources: [docker/Dockerfile:39-43]()

### Run-Time Variables — What They Cannot Do

Passing `ADMIN_USER`, `ADMIN_PASSWORD`, or `ADMIN_EMAIL` at `docker run` time has **no effect** on the embedded FeatureHub state. The setup pipeline ran during the build; `entrypoint.sh` does not re-run it. The admin that was created at build time is the only admin in the database, regardless of any run-time env var values.

`APP_URL` at run time also has no effect: `setup.js` is never invoked at runtime, and the server binds to its configured port (`8085`) from its own configuration files, not from the `APP_URL` env var.

> **Operator note**: The defaults (`admin` / `admin`) are insecure and intended for development and CI only. For any non-development image, override all `ADMIN_*` env vars at `docker build` time via `--build-arg` or by setting `ENV` values in a downstream Dockerfile.

Sources: [specs/dk-flags-party-server-image.md:168-175]()

---

## `run-setup.sh` — Manual Path (Not Part of the Image Build)

`run-setup.sh` is a standalone convenience script that polls readiness and runs `node /app/setup.js` against a server that is already running externally. It is **not called** by the Dockerfile or `build-setup.sh`:

```sh
# Wait up to 60s for $APP_URL, then:
node /app/setup.js
```

It serves as an operator escape hatch — for example, running the setup pipeline against a live container that was started without the baked initialization, or during local development. Its existence does not affect the normal image lifecycle.

Sources: [docker/run-setup.sh:1-29]()

---

## Summary

The `dk-flags-party-server` image achieves a self-contained, ready-on-start FeatureHub instance by running a two-phase lifecycle: a **build-time phase** that temporarily starts the embedded `bathe.BatheBooter`-launched party-server, drives a five-step Node.js initialization pipeline against the FeatureHub Management REST API, and then stops the server before committing the initialized filesystem as an image layer; and a **run-time phase** in which `entrypoint.sh` simply re-launches the same server in the foreground via `exec`, with no setup logic whatsoever. Admin credentials and the seed flag set are permanently baked at build time — run-time environment variables cannot retroactively change the embedded state, which means any change to flags or credentials requires a rebuild.

---

## 03. HTTP Client, Readiness Probe & Authentication

> The api.js module: makeRequest() signature, Bearer token injection, JSON vs text response handling, and the waitForApp() readiness loop (60 s MAX_WAIT_TIME). The auth.js module: the two-phase authenticate() flow — initializeAdmin() via POST /mr-api/initialize, falling back to login() via POST /mr-api/login when the system is already bootstrapped. Token extraction and propagation to downstream calls.

- Page Markdown: https://grok-wiki.com/public/wiki/vtex-dk-flags-0a8c140c3cfa/pages/03-http-client-readiness-probe-authentication.md
- Generated: 2026-05-26T16:09:49.857Z

### Source Files

- `docker/api.js`
- `docker/auth.js`
- `docker/config.js`
- `docker/setup.js`
- `specs/dk-flags-party-server-image.md`

<details>
<summary>Relevant source files</summary>
The following files were used as context for generating this wiki page:

- [docker/api.js](docker/api.js)
- [docker/auth.js](docker/auth.js)
- [docker/config.js](docker/config.js)
- [docker/setup.js](docker/setup.js)
- [specs/dk-flags-party-server-image.md](specs/dk-flags-party-server-image.md)
</details>

# HTTP Client, Readiness Probe & Authentication

The `docker/api.js` and `docker/auth.js` modules form the foundation layer of the `dk-flags` build-time initialization pipeline. Together they handle every outbound HTTP call, gate the rest of setup on a confirmed-ready server, and obtain the Bearer token that all subsequent FeatureHub Management API calls require. Because the entire pipeline runs at **image build time** (inside `docker build`, not on container start), reliability and deterministic failure behavior matter more than runtime flexibility.

This page covers the function signatures, request-construction rules, response-parsing strategy, readiness-probe timing, and the two-phase authentication flow in full detail.

---

## Module Overview

```text
config.js
 ├─ APP_URL, MAX_WAIT_TIME, ADMIN_*, PORTFOLIO_NAME …
 │
api.js  (imports config)
 ├─ makeRequest(method, path, data?, token?) → { status, data, headers }
 └─ waitForApp()                             → Promise<true> | throws
 │
auth.js  (imports config + api)
 ├─ initializeAdmin()  → { success, token }
 ├─ login()            → { success, token }
 └─ authenticate()     → Promise<token | null>
 │
setup.js  (imports api + auth + portfolio + features + flags)
 └─ orchestrator: waitForApp → authenticate → … → createAllFeatures
```

Sources: [docker/api.js:1-65](), [docker/auth.js:1-84](), [docker/config.js:1-10](), [docker/setup.js:1-82]()

---

## `api.js` — HTTP Client

### `makeRequest()` Signature

```js
// docker/api.js:3
async function makeRequest(method, path, data = null, token = null)
```

| Parameter | Type | Required | Notes |
|-----------|------|----------|-------|
| `method`  | string | yes | `'GET'`, `'POST'`, `'PUT'`, etc. |
| `path`    | string | yes | Relative path; resolved against `config.APP_URL` via `new URL(path, base)` |
| `data`    | object \| null | no | Serialized as JSON body when non-null |
| `token`   | string \| null | no | When set, injects `Authorization: Bearer <token>` |

Sources: [docker/api.js:3-10]()

### Request Construction

The function always sets `Content-Type: application/json`. The target URL is assembled with the native `URL` constructor, which means `path` must be a relative or absolute path string — not a full URL — when a base is already defined in `config.APP_URL`.

```js
// docker/api.js:4-15
const url = new URL(path, config.APP_URL);

const options = {
    method,
    headers: { 'Content-Type': 'application/json' }
};

if (token) {
    options.headers['Authorization'] = `Bearer ${token}`;
}

if (data) {
    options.body = JSON.stringify(data);
}
```

### Bearer Token Injection

Token injection is opt-in per call: `makeRequest` receives the token as an argument, checks for truthiness, and appends the `Authorization` header only when a token is present. The token is never stored in module state — callers (typically `auth.js` and the higher-level modules) thread it through explicitly.

Sources: [docker/api.js:13-15]()

### Response Handling: JSON vs. Text

After `fetch` resolves, `makeRequest` inspects the `content-type` response header to decide how to decode the body:

```js
// docker/api.js:23-30
const contentType = response.headers.get('content-type');
let responseData = null;

if (contentType && contentType.includes('application/json')) {
    responseData = await response.json();
} else {
    responseData = await response.text();
}
```

The resolved response envelope is:

```js
{ status: response.status, data: responseData, headers: Object.fromEntries(response.headers.entries()) }
```

`status` is always the numeric HTTP status code. `data` is a parsed object for JSON responses, a plain string for everything else. `headers` is a plain object (not `Headers`) so callers can destructure without the `Headers` API.

Sources: [docker/api.js:22-36]()

### Error Behavior

Network-level errors (fetch throws) are caught and re-thrown with the message prefix `"Request failed: …"`. HTTP error status codes (4xx, 5xx) are **not** thrown — they are returned as `{ status: N, data: … }` and the caller decides whether the status is acceptable.

Sources: [docker/api.js:37-39]()

---

## `api.js` — Readiness Probe: `waitForApp()`

### Behavior

`waitForApp()` issues plain `GET ${config.APP_URL}` requests in a 1-second polling loop until either the response is `ok` (HTTP 2xx) or the elapsed time exceeds `MAX_WAIT_TIME`.

```js
// docker/api.js:42-59
async function waitForApp() {
    console.log(`Waiting for app to be ready at ${config.APP_URL}...`);
    const startTime = Date.now();

    while (Date.now() - startTime < config.MAX_WAIT_TIME) {
        try {
            const response = await fetch(config.APP_URL);
            if (response.ok) {
                console.log('App is ready!');
                return true;
            }
        } catch (error) {
            // App not ready yet, continue waiting
        }
        await new Promise(resolve => setTimeout(resolve, 1000));
    }

    throw new Error('App did not become ready within the timeout period');
}
```

### Timing

`MAX_WAIT_TIME` is `60000` ms (60 seconds), set in `config.js`:

```js
// docker/config.js:6
MAX_WAIT_TIME: 60000, // 60 seconds
```

The shell script `build-setup.sh` enforces a separate outer limit of 120 s before calling `node setup.js`. The JS layer therefore operates within a window that the shell has already partially consumed. The spec notes this as a low-risk race: by the time `node setup.js` is invoked, the shell has already confirmed an HTTP-200 response at least once, so `waitForApp()` usually succeeds on its first attempt.

Sources: [docker/api.js:46](), [docker/config.js:6](), [specs/dk-flags-party-server-image.md:83]()

### Failure Mode

When 60 s elapse without an `ok` response, `waitForApp()` throws `Error('App did not become ready within the timeout period')`. The `setup()` orchestrator in `setup.js` does not catch this — it propagates up, causes `setup.js` to exit 1, and `build-setup.sh` treats that as a build failure.

---

## `auth.js` — Two-Phase Authentication

### Flow Overview

```mermaid
sequenceDiagram
    participant S as setup.js
    participant A as authenticate()
    participant I as initializeAdmin()
    participant L as login()
    participant API as FeatureHub /mr-api

    S->>A: authenticate()
    A->>I: initializeAdmin()
    I->>API: POST /mr-api/initialize
    alt status 200 or 201
        API-->>I: { accessToken | token }
        I-->>A: { success: true, token }
        A-->>S: token
    else initialization failed (already set up or error)
        API-->>I: other status
        I-->>A: { success: false, token: null }
        A->>L: login()
        L->>API: POST /mr-api/login
        alt status 200 or 201
            API-->>L: { accessToken | token }
            L-->>A: { success: true, token }
            A-->>S: token
        else login failed
            L-->>A: { success: false, token: null }
            A-->>S: null
        end
    end
```

### Phase 1: `initializeAdmin()`

`initializeAdmin()` calls `POST /mr-api/initialize` — the FeatureHub SetupService endpoint that creates the initial site admin, portfolio, and organization in a single bootstrap call. It is only callable on a fresh, uninitialized FeatureHub instance.

```js
// docker/auth.js:8-14
const response = await makeRequest('POST', '/mr-api/initialize', {
    portfolio: config.PORTFOLIO_NAME,
    organizationName: config.ORGANIZATION_NAME,
    emailAddress: config.ADMIN_EMAIL,
    password: config.ADMIN_PASSWORD,
    name: config.ADMIN_USER
});
```

Request body fields:

| Field | Config source | Default |
|-------|--------------|---------|
| `portfolio` | `PORTFOLIO_NAME` | `'integrationTest'` |
| `organizationName` | `ORGANIZATION_NAME` | `'vtex'` |
| `emailAddress` | `ADMIN_EMAIL` | `'admin@example.com'` |
| `password` | `ADMIN_PASSWORD` | `'admin'` |
| `name` | `ADMIN_USER` | `'admin'` |

Sources: [docker/auth.js:8-14](), [docker/config.js:1-10]()

### Phase 2: `login()`

When `initializeAdmin()` does not return a 200/201 — indicating the system was already bootstrapped on a previous build — `authenticate()` falls back to `login()`:

```js
// docker/auth.js:36-39
const response = await makeRequest('POST', '/mr-api/login', {
    email: config.ADMIN_EMAIL,
    password: config.ADMIN_PASSWORD
});
```

Sources: [docker/auth.js:36-39]()

### Token Extraction

Both `initializeAdmin()` and `login()` use the same extraction pattern:

```js
// docker/auth.js:20 (initializeAdmin) and auth.js:44 (login)
const token = response.data.accessToken || response.data.token;
```

The `||` fallback accommodates FeatureHub API versions that use either field name. If both are absent (e.g., the server returned a non-JSON body), `token` is `undefined`, which is coerced to falsy in the caller check.

Sources: [docker/auth.js:20](), [docker/auth.js:44]()

### `authenticate()` Orchestration

```js
// docker/auth.js:57-77
async function authenticate() {
    const initResult = await initializeAdmin();

    if (initResult.success && initResult.token) {
        return initResult.token;
    }

    if (!initResult.success) {
        // … log warnings …
        const loginResult = await login();
        if (loginResult.success && loginResult.token) {
            return loginResult.token;
        }
    }

    return null;
}
```

`authenticate()` returns the raw token string on success or `null` when both phases fail. It never throws. Callers check for `null` and abort the pipeline with a warning log (not a thrown error) when no token can be obtained.

Sources: [docker/auth.js:57-77](), [docker/setup.js:17-22]()

---

## Token Propagation to Downstream Calls

Once `setup.js` obtains the token from `authenticate()`, it passes it explicitly to every subsequent call:

```js
// docker/setup.js:27-61
const portfolioId = await getPortfolio(token);
const applicationId = await createApplication(portfolioId, token);
const environmentId = await getEnvironment(applicationId, token);
// …
const result = await createAllFeatures(applicationId, environmentId, flags, token);
```

There is no global token store or module-level variable. Each function in `portfolio.js` and `features.js` receives the token as its last argument and passes it to `makeRequest`, which injects it as `Authorization: Bearer <token>`. This design keeps the token flow explicit and the modules independently testable.

Sources: [docker/setup.js:27-61]()

---

## Configuration Reference

All values used by `api.js` and `auth.js` come from `docker/config.js`, which reads environment variables with hard-coded defaults:

| Constant | Env var | Default | Used by |
|----------|---------|---------|---------|
| `APP_URL` | `APP_URL` | `http://localhost:8085` | `waitForApp()`, `makeRequest()` base URL |
| `MAX_WAIT_TIME` | — (hard-coded) | `60000` ms | `waitForApp()` loop ceiling |
| `ADMIN_USER` | `ADMIN_USER` | `admin` | `initializeAdmin()` `name` field |
| `ADMIN_PASSWORD` | `ADMIN_PASSWORD` | `admin` | both auth phases |
| `ADMIN_EMAIL` | `ADMIN_EMAIL` | `admin@example.com` | both auth phases |
| `PORTFOLIO_NAME` | — | `integrationTest` | `initializeAdmin()` `portfolio` field |
| `ORGANIZATION_NAME` | — | `vtex` | `initializeAdmin()` `organizationName` field |

Sources: [docker/config.js:1-10]()

The `ADMIN_*` values are baked into the image layer at build time. Overriding them at `docker run` time has no effect on the embedded credentials because setup does not re-run on container start.

---

## Summary

`api.js` provides a single, uniform HTTP client (`makeRequest`) that handles Bearer token injection, JSON/text response detection, and error normalization, plus a polling readiness probe (`waitForApp`) that gates setup on a live server within a 60-second window. `auth.js` wraps that client with a two-phase strategy: attempt a first-time bootstrap via `POST /mr-api/initialize` (which seeds the admin, portfolio, and organization simultaneously), and fall back to `POST /mr-api/login` when the system has already been initialized. The extracted token is threaded explicitly through every downstream call — there is no implicit global state — making each module independently verifiable and the authorization path straightforward to audit.

---

## 04. Portfolio, Application & Feature Flag Provisioning

> portfolio.js: getPortfolio() (selects the first portfolio from GET /mr-api/portfolio), createApplication() (POST to portfolio endpoint), getEnvironment() (resolves default environment). features.js: the createAllFeatures() orchestration loop — createFeature() (idempotent, 409 treated as skipped), unlockBooleanFlag() (BOOLEAN-only unlock step via PUT feature-environments), setFeatureValue() (typed value dispatch: valueBoolean / valueString / valueNumber / valueJson), and the created/skipped/errors summary. flags.js: loadFlags() 4-path search strategy.

- Page Markdown: https://grok-wiki.com/public/wiki/vtex-dk-flags-0a8c140c3cfa/pages/04-portfolio-application-feature-flag-provisioning.md
- Generated: 2026-05-26T16:10:02.232Z

### Source Files

- `docker/portfolio.js`
- `docker/features.js`
- `docker/flags.js`
- `docker/config.js`
- `docker/setup.js`
- `docker/exampleFlags.json`

<details>
<summary>Relevant source files</summary>
The following files were used as context for generating this wiki page:

- [docker/setup.js](docker/setup.js)
- [docker/portfolio.js](docker/portfolio.js)
- [docker/features.js](docker/features.js)
- [docker/flags.js](docker/flags.js)
- [docker/config.js](docker/config.js)
- [docker/api.js](docker/api.js)
- [docker/exampleFlags.json](docker/exampleFlags.json)
</details>

# Portfolio, Application & Feature Flag Provisioning

This page covers the end-to-end provisioning pipeline that `setup.js` drives against a running FeatureHub instance: selecting a portfolio, creating an application inside it, resolving the default environment, loading a flag definition file, and idempotently seeding every feature flag with its typed default value. Understanding this flow is essential for anyone bootstrapping a local or CI FeatureHub instance, writing integration tests, or extending the flag catalog.

The pipeline is intentionally sequential — each step produces an identifier that the next step consumes — so any failure short-circuits the run and prints a diagnostic without crashing the container process.

---

## Overall Orchestration

`setup.js` is the entry point. It runs as a one-shot Node script and exits with code `0` on success or `1` on unrecoverable error.

```
setup()
  1. waitForApp()           — poll APP_URL until HTTP 200 (up to 60 s)
  2. authenticate()         — obtain Bearer token
  3. getPortfolio()         — resolve portfolioId
  4. createApplication()    — create app inside portfolio → applicationId
  5. getEnvironment()       — resolve environmentId
  6. loadFlags()            — read exampleFlags.json → flags[]
  7. createAllFeatures()    — seed every flag
```

Sources: [docker/setup.js:8-73](docker/setup.js)

If `getPortfolio`, `createApplication`, `getEnvironment`, or `loadFlags` returns a falsy value, the script logs a warning and returns early without throwing — this keeps the Docker entrypoint from restarting indefinitely when FeatureHub is intentionally empty.

---

## Configuration (`config.js`)

All runtime knobs are resolved once from environment variables with safe defaults:

| Key | Env variable | Default |
|---|---|---|
| `APP_URL` | `APP_URL` | `http://localhost:8085` |
| `ADMIN_USER` | `ADMIN_USER` | `admin` |
| `ADMIN_PASSWORD` | `ADMIN_PASSWORD` | `admin` |
| `ADMIN_EMAIL` | `ADMIN_EMAIL` | `admin@example.com` |
| `MAX_WAIT_TIME` | _(hardcoded)_ | `60000` ms |
| `PORTFOLIO_NAME` | _(hardcoded)_ | `integrationTest` |
| `APPLICATION_NAME` | _(hardcoded)_ | `integrationTest` |

Sources: [docker/config.js:1-10](docker/config.js)

`PORTFOLIO_NAME` and `APPLICATION_NAME` are not currently wired to env variables; they are fixed constants used as the canonical test portfolio and application names.

---

## Portfolio Module (`portfolio.js`)

### `getPortfolio(token)`

Issues `GET /mr-api/portfolio` and selects the **first** portfolio from the response. The function is defensive about the response envelope — it checks `response.data` as a plain array first, then falls back to `.items`, `.portfolios`, or `.data` sub-keys before giving up. Returns the first resolved `id` / `portfolioId` / `portfolio.id`, or `null` if none are found.

```js
// docker/portfolio.js:8-21
const response = await makeRequest('GET', '/mr-api/portfolio', null, token);
const portfolios = Array.isArray(response.data) ? response.data :
    (response.data.items || response.data.portfolios || response.data.data || []);
if (portfolios.length > 0) {
    const portfolioId = portfolios[0].id || portfolios[0].portfolioId || portfolios[0].portfolio?.id;
    return portfolioId;
}
```

This "first wins" strategy means the script does not create a portfolio — it expects one to exist already (created manually or by a prior run).

### `createApplication(portfolioId, token)`

Posts to `POST /mr-api/portfolio/{portfolioId}/application` with the body `{ name: config.APPLICATION_NAME, description: 'Example application' }`. Accepts HTTP 200 or 201 as success and returns `response.data.id`. Any other status is logged and returns `null`.

Sources: [docker/portfolio.js:29-52](docker/portfolio.js)

### `getEnvironment(applicationId, token)`

Issues `GET /mr-api/application/{applicationId}/environment` and picks the first item from the array response, returning `data[0].id`. Like `getPortfolio`, it does not create an environment — it relies on FeatureHub auto-creating a default environment when the application is provisioned.

Sources: [docker/portfolio.js:54-74](docker/portfolio.js)

---

## Feature Flag Module (`features.js`)

### Flag Definition Shape

Each flag in `exampleFlags.json` follows this schema:

| Field | Required | Description |
|---|---|---|
| `key` | yes | Stable identifier used as the flag key in all API calls |
| `type` | yes | `BOOLEAN`, `STRING`, `NUMBER`, or `JSON` |
| `defaultValue` | yes | Value to set on the default environment |
| `name` | no | Human-readable name (falls back to `key`) |
| `description` | no | Falls back to `"Feature flag: {key}"` |

Sources: [docker/exampleFlags.json:1-55](docker/exampleFlags.json)

### `createFeature(applicationId, flag, token)`

Posts `{ name, key, description, valueType }` to `POST /mr-api/application/{applicationId}/features`.

| Response | Outcome |
|---|---|
| 200 / 201 | `{ success: true, skipped: false }` — flag created |
| 409 | `{ success: true, skipped: true }` — flag already exists, treat as no-op |
| Other | `{ success: false, skipped: false }` — creation failure |

The 409-as-skip pattern makes the entire provisioning run **idempotent** — re-running against an already-seeded FeatureHub instance does not fail; it counts existing flags as "skipped."

Sources: [docker/features.js:3-37](docker/features.js)

### `unlockBooleanFlag(applicationId, flagKey, environmentId, defaultValue, version, token)`

Called **only for `BOOLEAN` flags**, immediately after `createFeature` succeeds. Sends a `PUT` to `/mr-api/application/{applicationId}/feature-environments/{flagKey}` with the payload:

```js
[{
    key: flagKey,
    locked: false,
    environmentId: environmentId,
    valueBoolean: defaultValue,
    version: version
}]
```

This step exists because FeatureHub creates new BOOLEAN features in a locked state. The unlock must precede `setFeatureValue` for the value write to succeed. Non-boolean types do not have this locking behavior, so the unlock step is skipped for them.

Sources: [docker/features.js:39-64](docker/features.js)

### `setFeatureValue(environmentId, flag, version, token)`

Dispatches to `PUT /mr-api/features/{environmentId}/feature/{flagKey}` with exactly one of four typed value fields populated, all others set to `null`:

```js
{
    key: flagKey,
    locked: false,
    valueString:  flagType === 'STRING'  ? flag.defaultValue : null,
    valueNumber:  flagType === 'NUMBER'  ? flag.defaultValue : null,
    valueBoolean: flagType === 'BOOLEAN' ? flag.defaultValue : null,
    valueJson:    flagType === 'JSON'    ? JSON.stringify(flag.defaultValue) : null,
    version: version
}
```

`valueJson` serializes the JavaScript object/array to a JSON string before sending, matching the FeatureHub API expectation. On success, returns `{ success: true, newVersion: version + 1 }`, advancing the version counter for the next write in the loop.

Sources: [docker/features.js:66-98](docker/features.js)

### `createAllFeatures(applicationId, environmentId, flags, token)` — Orchestration Loop

Iterates over every flag definition in order, applying a three-step sub-sequence:

```
for each flag:
  1. createFeature()          → success / skipped / error
  2. if BOOLEAN: unlockBooleanFlag()
  3. setFeatureValue()        → success / error; version++
```

A shared `version` counter starts at `0` and increments after each successful `setFeatureValue` call (and also after each `unlockBooleanFlag` call, since the unlock itself advances the version). Running totals for `created`, `skipped`, and `errors` are accumulated and printed as a summary at the end:

```
Feature flags creation summary:
  ✓ Created: N
  ⊘ Skipped (already exists): N
  ✗ Errors: N
```

The function returns the summary object `{ created, skipped, errors }` to the caller in `setup.js`, which uses it only to emit an additional warning when `errors > 0 && created === 0`.

Sources: [docker/features.js:100-147](docker/features.js)

---

## Flags Loader (`flags.js`)

`loadFlags()` resolves `exampleFlags.json` via a four-path search, stopping at the first readable file:

| Priority | Path |
|---|---|
| 1 | `__dirname/exampleFlags.json` (same directory as `flags.js`) |
| 2 | `process.cwd()/exampleFlags.json` (working directory at runtime) |
| 3 | `/app/exampleFlags.json` (Docker container absolute path) |
| 4 | `./exampleFlags.json` (relative CWD shorthand) |

Each candidate is tested with `fs.existsSync` before reading, and parse errors skip silently to the next path. If no path yields a valid file, `loadFlags` returns `null` and logs a warning — `setup.js` then exits early without throwing.

Sources: [docker/flags.js:4-29](docker/flags.js)

The priority ordering places the script-adjacent path first (correct for local runs) and the Docker absolute path third (correct for container deployments where the working directory may differ from the script location).

---

## End-to-End Sequence

```mermaid
sequenceDiagram
    participant S as setup.js
    participant P as portfolio.js
    participant F as features.js
    participant FL as flags.js
    participant API as FeatureHub REST API

    S->>API: GET /mr-api/portfolio
    API-->>S: portfolioId
    S->>API: POST /mr-api/portfolio/{id}/application
    API-->>S: applicationId
    S->>API: GET /mr-api/application/{id}/environment
    API-->>S: environmentId
    S->>FL: loadFlags()
    FL-->>S: flags[]
    loop for each flag
        S->>API: POST /mr-api/application/{appId}/features
        API-->>S: 201 (created) or 409 (exists)
        opt BOOLEAN only
            S->>API: PUT /mr-api/application/{appId}/feature-environments/{key}
        end
        S->>API: PUT /mr-api/features/{envId}/feature/{key}
    end
    S-->>S: print summary
```

---

## Example Flag Catalog (`exampleFlags.json`)

The bundled catalog covers all four supported value types:

| Key | Type | Default Value |
|---|---|---|
| `booleanConf` | `BOOLEAN` | `true` |
| `stringConf` | `STRING` | `"myStringConf"` |
| `intConf` | `NUMBER` | `10` |
| `floatConf` | `NUMBER` | `1.5` |
| `jsonConf` | `JSON` | `{ "data": "test", "evaluation": true }` |
| `objectConf` | `JSON` | Nested object with string, int, bool, and array fields |
| `stringArrayConf` | `JSON` | `["stringValue1", "stringValue2", "stringValue3"]` |

Sources: [docker/exampleFlags.json:1-55](docker/exampleFlags.json)

The `JSON` type is used for both object and array payloads; there is no separate `ARRAY` type. Arrays are expressed as top-level JSON values and serialized with `JSON.stringify` in `setFeatureValue`.

---

## HTTP Transport (`api.js`)

All modules share `makeRequest(method, path, data, token)` from `api.js`. It resolves the full URL against `config.APP_URL`, sets `Content-Type: application/json`, attaches a `Bearer` token when provided, and returns `{ status, data, headers }`. JSON responses are parsed automatically; non-JSON responses are returned as raw text. Connection errors bubble up as thrown `Error` instances.

Sources: [docker/api.js:3-40](docker/api.js)

---

## Summary

The provisioning pipeline is a linear, fail-fast chain: portfolio → application → environment → flags. The modular design keeps each responsibility in its own file (`portfolio.js`, `features.js`, `flags.js`) with `setup.js` as the thin orchestrator. Idempotency is achieved via 409-as-skip at the feature creation layer, and the BOOLEAN-specific unlock step is the only branching logic inside the per-flag loop. The four-path file resolution in `loadFlags` handles both local development and containerized deployment without requiring runtime configuration.

---

## 05. Flag Definition Schema & Supported Types

> The exampleFlags.json contract: required fields (key, type, defaultValue), optional fields (name, description), and the four supported type values (BOOLEAN, STRING, NUMBER, JSON). How each type maps to a typed value field in the FeatureHub API (valueBoolean, valueString, valueNumber, valueJson). The special BOOLEAN unlock step. How to add, remove, or change seed flags and rebuild. The seven seed flags shipped by default.

- Page Markdown: https://grok-wiki.com/public/wiki/vtex-dk-flags-0a8c140c3cfa/pages/05-flag-definition-schema-supported-types.md
- Generated: 2026-05-26T16:12:12.800Z

### Source Files

- `docker/exampleFlags.json`
- `docker/features.js`
- `docker/flags.js`
- `specs/dk-flags-party-server-image.md`

<details>
<summary>Relevant source files</summary>
The following files were used as context for generating this wiki page:

- [docker/exampleFlags.json](docker/exampleFlags.json)
- [docker/features.js](docker/features.js)
- [docker/flags.js](docker/flags.js)
- [specs/dk-flags-party-server-image.md](specs/dk-flags-party-server-image.md)
</details>

# Flag Definition Schema & Supported Types

`docker/exampleFlags.json` is the single source of truth for the seed feature flags baked into the `dk-flags-party-server` Docker image at build time. Every entry in this file is read, validated, and submitted to the FeatureHub Management REST API during `docker build`, so the resulting image starts with all flags already created and their default values set.

Understanding the schema is the prerequisite for adding, removing, or changing flags, because any modification requires a Docker image rebuild to take effect. This page documents every field the schema accepts, the four supported type values, how each type maps to a typed API field, and the special unlock step that applies only to `BOOLEAN` flags.

---

## Schema Fields

Each element of the top-level JSON array represents one feature flag. The schema supports five fields.

| Field | Required | Type | Default when omitted |
|---|---|---|---|
| `key` | Yes | `string` | — |
| `type` | Yes | `"BOOLEAN" \| "STRING" \| "NUMBER" \| "JSON"` | — |
| `defaultValue` | Yes | matches `type` | — |
| `name` | No | `string` | falls back to `key` |
| `description` | No | `string` | falls back to `"Feature flag: ${key}"` |

Sources: [docker/features.js:3-7]()

### Required Fields

**`key`** — the FeatureHub flag key. This is the identifier used in every subsequent API call (`POST /mr-api/application/{id}/features`, `PUT /mr-api/features/{envId}/feature/{key}`). It must be unique within the application.

**`type`** — one of the four string literals `BOOLEAN`, `STRING`, `NUMBER`, or `JSON`. This value is passed directly as `valueType` in the feature-creation payload and determines which typed value field is populated during the value-set step.

**`defaultValue`** — the initial value baked into the environment at build time. The JavaScript type must match the declared `type`: a native boolean for `BOOLEAN`, a native string for `STRING`, a native number for `NUMBER`, and a JSON-serializable object or array for `JSON`.

### Optional Fields

**`name`** — a human-readable display name shown in the FeatureHub Management UI. When omitted, the flag `key` is used as the name.

**`description`** — a short description rendered in the UI. When omitted, the setup pipeline generates `"Feature flag: ${key}"`.

Sources: [docker/features.js:3-8]()

---

## Supported Types and API Mapping

When `setFeatureValue` submits a `PUT /mr-api/features/{environmentId}/feature/{key}` request, it constructs a value payload with four typed fields — only one of which is non-null depending on the flag's `type`. The other three fields are explicitly set to `null`.

```js
// docker/features.js
const valueData = {
    key: flagKey,
    locked: false,
    valueString: flagType === 'STRING'  ? flag.defaultValue : null,
    valueNumber: flagType === 'NUMBER'  ? flag.defaultValue : null,
    valueBoolean: flagType === 'BOOLEAN' ? flag.defaultValue : null,
    valueJson:   flagType === 'JSON'    ? JSON.stringify(flag.defaultValue) : null,
    version: version
};
```

Sources: [docker/features.js:73-81]()

| `type` in schema | Populated API field | JavaScript value |
|---|---|---|
| `BOOLEAN` | `valueBoolean` | native `true` / `false` |
| `STRING` | `valueString` | string literal |
| `NUMBER` | `valueNumber` | integer or float |
| `JSON` | `valueJson` | `JSON.stringify(defaultValue)` — object or array |

A flag whose `type` is not one of these four literals will cause all four value fields to be `null` in the payload. The FeatureHub API will accept the feature-creation step but the value will not be meaningful.

Sources: [specs/dk-flags-party-server-image.md:334]()

---

## The BOOLEAN Unlock Step

FeatureHub locks newly created feature flags by default. For `BOOLEAN` flags specifically, a dedicated unlock call is required **before** the value-set call; without it the environment cannot record the boolean default.

The pipeline detects the flag type and branches:

```js
// docker/features.js (createAllFeatures)
if (flag.type === 'BOOLEAN') {
    await unlockBooleanFlag(applicationId, flag.key, environmentId, flag.defaultValue, version, token);
    version++;
}
// Set feature value (all types)
const valueResult = await setFeatureValue(environmentId, flag, version, token);
```

Sources: [docker/features.js:119-127]()

The `unlockBooleanFlag` function issues a `PUT /mr-api/application/{id}/feature-environments/{key}` with `locked: false` and the `valueBoolean` field set to the flag's `defaultValue`. This is the only type-conditional branch in the whole pipeline; all other types skip directly to the value-set call, which itself sets `locked: false` in the body.

Sources: [docker/features.js:43-49](), [specs/dk-flags-party-server-image.md:215-217]()

```text
BOOLEAN flag lifecycle during build
────────────────────────────────────
 POST /mr-api/application/{id}/features        ← create (locked by default)
 PUT  /mr-api/application/{id}/feature-environments/{key}  ← unlock + set valueBoolean
 PUT  /mr-api/features/{envId}/feature/{key}   ← set typed value (locked:false)

STRING / NUMBER / JSON flag lifecycle
───────────────────────────────────────
 POST /mr-api/application/{id}/features        ← create
 PUT  /mr-api/features/{envId}/feature/{key}   ← set typed value (locked:false)
```

---

## The Seven Default Seed Flags

`docker/exampleFlags.json` ships seven seed flags that cover every supported type, including two distinct `NUMBER` shapes (integer and float) and three distinct `JSON` shapes (flat object, nested object, and array).

| `key` | `type` | `defaultValue` |
|---|---|---|
| `booleanConf` | `BOOLEAN` | `true` |
| `stringConf` | `STRING` | `"myStringConf"` |
| `intConf` | `NUMBER` | `10` |
| `floatConf` | `NUMBER` | `1.5` |
| `jsonConf` | `JSON` | `{ "data": "test", "evaluation": true }` |
| `objectConf` | `JSON` | nested object with string, int, bool, and array attributes |
| `stringArrayConf` | `JSON` | `["stringValue1", "stringValue2", "stringValue3"]` |

Sources: [docker/exampleFlags.json:1-54]()

The `JSON` type accepts any JSON-serializable value — a flat object, a deeply nested object, or a plain array — because `setFeatureValue` calls `JSON.stringify(flag.defaultValue)` before sending the payload. The `objectConf` entry demonstrates nested objects with mixed attribute types, while `stringArrayConf` demonstrates that a bare array is equally valid.

---

## Loading the File at Build Time

`docker/flags.js` exposes a single `loadFlags()` function that searches four candidate paths for the file and parses it:

```js
const possiblePaths = [
    path.join(__dirname, 'exampleFlags.json'),
    path.join(process.cwd(), 'exampleFlags.json'),
    '/app/exampleFlags.json',
    './exampleFlags.json'
];
```

Sources: [docker/flags.js:5-10]()

The first path that exists and parses successfully is used. If none is found, `loadFlags()` returns `null` and `setup.js` exits with a "No flags to create" message — the image still builds successfully, but no flags are seeded. This behavior makes a missing file a recoverable situation rather than a hard build failure.

---

## Adding, Removing, or Changing Seed Flags

Changes to `docker/exampleFlags.json` are **not** hot-reloaded. The file is read once at `docker build` time; a running container cannot pick up edits. The workflow is:

1. Edit `docker/exampleFlags.json` — add, remove, or modify entries following the schema above.
2. Rebuild the image: `docker build -f docker/Dockerfile -t dk-flags-party-server .`
3. Start a new container from the updated image.

A flag present in a running container but removed from `exampleFlags.json` will remain in FeatureHub after rebuild (there is no reconciliation or deletion step; `409 Conflict` on creation is silently treated as "already exists" / skipped). If a flag that already exists in FeatureHub is encountered, the creation call returns `409` and the pipeline counts it as `skipped` rather than `error`, keeping the run idempotent.

Sources: [specs/dk-flags-party-server-image.md:39-42](), [docker/features.js:26-29]()

---

## Summary

`docker/exampleFlags.json` is a JSON array of flag definitions. Each entry requires `key`, `type`, and `defaultValue`; `name` and `description` are optional. The four legal `type` values are `BOOLEAN`, `STRING`, `NUMBER`, and `JSON`, each mapping to a dedicated typed field in the FeatureHub value-set API call (`valueBoolean`, `valueString`, `valueNumber`, `valueJson`). `JSON` typed flags accept any JSON-serializable value and are stringified before submission. `BOOLEAN` flags require an extra unlock API call before the value-set call, which is the only type-conditional branch in the pipeline. The repository ships seven seed flags demonstrating all types. Modifying the seed set requires editing the file and rebuilding the Docker image.

---

## 06. Build, Deploy & Extension Points

> How to build (docker build -f docker/Dockerfile) and run (docker run -p 8085:8085) the image with default and overridden env vars. The CI/CD pipeline: dkcicd build-and-push-image-v1 on push to main, publishing to AWS ECR account 053131491888 / us-east-1 / dk-flags-party-server:0.0.1. Production endpoint https://flags.vtex.com/. Backstage catalog registration (te-0034 / OG35ZXTZ). Downstream OpenFeature SDK consumers. Known risks: floating base image tag, mutable 0.0.1 ECR tag, build-time-only admin baking. Outstanding follow-ups from the spec: reconcile package.json#main, make portfolio/org/app names env-overridable, pin the base image.

- Page Markdown: https://grok-wiki.com/public/wiki/vtex-dk-flags-0a8c140c3cfa/pages/06-build-deploy-extension-points.md
- Generated: 2026-05-26T16:12:04.918Z

### Source Files

- `docker/Dockerfile`
- `.vtex/deployment.yaml`
- `.vtex/catalog-info.yaml`
- `docker/config.js`
- `docker/build-setup.sh`
- `specs/dk-flags-party-server-image.md`
- `README.md`

<details>
<summary>Relevant source files</summary>
The following files were used as context for generating this wiki page:

- [docker/Dockerfile](docker/Dockerfile)
- [docker/build-setup.sh](docker/build-setup.sh)
- [docker/entrypoint.sh](docker/entrypoint.sh)
- [docker/config.js](docker/config.js)
- [docker/package.json](docker/package.json)
- [.vtex/deployment.yaml](.vtex/deployment.yaml)
- [.vtex/catalog-info.yaml](.vtex/catalog-info.yaml)
- [specs/dk-flags-party-server-image.md](specs/dk-flags-party-server-image.md)
- [README.md](README.md)
</details>

# Build, Deploy & Extension Points

This page covers how the `dk-flags-party-server` Docker image is built, published, and consumed. It explains the build-time initialization pipeline that pre-seeds a FeatureHub instance, the CI/CD configuration that publishes the image to AWS ECR on every push to `main`, the production endpoint this image backs, and the catalog metadata that registers the component in the VTEX service catalog. It also documents the extension points available to operators (environment variable overrides, seed flag additions) and the known risks and outstanding follow-up items that consumers should be aware of.

The core design choice—running FeatureHub initialization **at image build time** rather than at container start—means a `docker run` yields a fully seeded, immediately usable flag server with no post-start configuration steps. The trade-off is that admin credentials and flag seeds are immutable for a given image tag; changing them requires a rebuild.

---

## Building the Image

### Command

```bash
docker build -f docker/Dockerfile -t dk-flags-party-server .
```

The Dockerfile is at `docker/Dockerfile`. The build context must be the repository root (`.`) because the `COPY` instructions reference paths under `docker/`.

Sources: [docker/Dockerfile:1-54]()

### What the Build Does

The Dockerfile extends `featurehub/party-server:latest`, installs Node.js and supporting tools via Alpine's `apk`, copies the modular setup scripts, then runs `build-setup.sh` as a `RUN` layer to initialize the embedded FeatureHub server before the image is finalized:

```dockerfile
FROM featurehub/party-server:latest

RUN set -eux; \
    apk add --no-cache nodejs npm curl procps;

WORKDIR /app
COPY docker/package.json ./
RUN npm install

COPY docker/setup.js ./
COPY docker/config.js ./
COPY docker/api.js ./
COPY docker/auth.js ./
COPY docker/portfolio.js ./
COPY docker/features.js ./
COPY docker/flags.js ./
COPY docker/exampleFlags.json ./
COPY docker/entrypoint.sh ./
COPY docker/build-setup.sh ./

RUN chmod +x entrypoint.sh build-setup.sh

ENV APP_URL=http://localhost:8085
ENV ADMIN_USER=admin
ENV ADMIN_PASSWORD=admin
ENV ADMIN_EMAIL=admin@example.com

RUN /app/build-setup.sh

EXPOSE 8085
ENTRYPOINT ["/app/entrypoint.sh"]
CMD []
```

Sources: [docker/Dockerfile:1-54]()

### Build-Time Initialization Pipeline

`build-setup.sh` orchestrates a three-phase sequence inside the `RUN` layer:

1. **Start the embedded server.** The script probes for the party-server JAR in `/app/classpath/` and launches it via `bathe.BatheBooter` with the classpath from `/app/classpath/*` and `/app/libs/*`. Several fallback launch strategies are tried in order (jar in `/app`, `featurehub-party-server` command, common paths) to survive base-image layout changes.
2. **Wait for readiness.** Polls `GET ${APP_URL}` every 2 seconds for up to 120 seconds. Exits with an error and dumps the last 50 lines of `/tmp/server.log` if the server does not become reachable.
3. **Run `node /app/setup.js`.** The orchestrator script calls the FeatureHub Management REST API to create admin + portfolio + application + environment + flags.
4. **Stop the server.** Sends `kill`, waits 2 seconds, then `kill -9` if the process is still alive.

```
build-setup.sh
  ├── start_server()             # bathe.BatheBooter → /tmp/server.log
  ├── poll $APP_URL (max 120 s)
  ├── node /app/setup.js         # seed admin + flags via REST
  └── kill $SERVER_PID           # graceful → force if needed
```

Sources: [docker/build-setup.sh:1-128]()

### `setup.js` REST API Flow

`setup.js` is the orchestrator. It calls the FeatureHub Management API through six modular helper modules:

| Module | Responsibility |
|---|---|
| `config.js` | Exports env values (`APP_URL`, `ADMIN_*`, `MAX_WAIT_TIME`) and hard-coded names (`PORTFOLIO_NAME`, `ORGANIZATION_NAME`, `APPLICATION_NAME`) |
| `api.js` | `makeRequest()` (HTTP + auth header), `waitForApp()` (60 s readiness probe) |
| `auth.js` | `initializeAdmin()` via `POST /mr-api/initialize`; falls back to `POST /mr-api/login` if already initialized |
| `portfolio.js` | Discovers portfolio by `GET /mr-api/portfolio`; creates application; resolves default environment |
| `features.js` | Creates feature definitions, unlocks `BOOLEAN` flags, and sets typed values per flag |
| `flags.js` | Loads `exampleFlags.json` from one of four candidate paths |

```mermaid
sequenceDiagram
    participant B as build-setup.sh
    participant S as party-server (build time)
    participant SU as setup.js
    participant API as FeatureHub Mgmt API

    B->>S: start via bathe.BatheBooter
    B->>S: poll APP_URL (max 120 s)
    B->>SU: node /app/setup.js
    SU->>API: POST /mr-api/initialize (or fallback /mr-api/login)
    API-->>SU: token
    SU->>API: GET /mr-api/portfolio
    SU->>API: POST /mr-api/portfolio/{id}/application
    SU->>API: GET /mr-api/application/{id}/environment
    loop each flag in exampleFlags.json
        SU->>API: POST /mr-api/application/{id}/features
        alt type == BOOLEAN
            SU->>API: PUT /mr-api/application/{id}/feature-environments/{key} (unlock)
        end
        SU->>API: PUT /mr-api/features/{envId}/feature/{key} (set typed value)
    end
    SU-->>B: exit 0
    B->>S: kill (graceful → force after ~2 s)
```

Sources: [specs/dk-flags-party-server-image.md:299-317](), [docker/config.js:1-10]()

---

## Environment Variables

All four variables have Dockerfile `ENV` defaults. They can be overridden at **build time** (affecting the admin baked into the image) or at **run time** (affecting the server's behavior, but **not** rewriting the already-embedded admin data).

| Variable | Default | Build-time effect | Run-time effect |
|---|---|---|---|
| `APP_URL` | `http://localhost:8085` | URL that `build-setup.sh` polls and `setup.js` targets | URL passed to the server process |
| `ADMIN_USER` | `admin` | Admin display name passed to `/mr-api/initialize` | No effect on embedded admin |
| `ADMIN_PASSWORD` | `admin` | Admin password baked into the image | No effect on embedded admin |
| `ADMIN_EMAIL` | `admin@example.com` | Admin login email baked into the image | No effect on embedded admin |

> **Security note**: The defaults are intentionally insecure and intended for local development and integration testing only. Production builds must override all `ADMIN_*` variables at build time. See [Known Risks](#known-risks) below.

Sources: [docker/Dockerfile:39-43](), [docker/config.js:1-10](), [specs/dk-flags-party-server-image.md:280-284]()

---

## Running the Container

### Default run

```bash
docker run -p 8085:8085 dk-flags-party-server
```

FeatureHub is available at `http://localhost:8085`. The pre-seeded admin is `admin@example.com` / `admin`.

### Custom credentials at run time

```bash
docker run -p 8085:8085 \
  -e ADMIN_USER=myadmin \
  -e ADMIN_PASSWORD=mypassword \
  -e ADMIN_EMAIL=admin@myorg.com \
  dk-flags-party-server
```

These env vars are visible to the server process but **do not** re-run setup. The admin embedded at build time remains unchanged.

### What `entrypoint.sh` does

The entrypoint launches the party-server in the foreground using `exec` (replacing the shell process) via the same `bathe.BatheBooter` strategy as `build-setup.sh`, with no setup re-run:

```sh
exec java -cp "/app/classpath/*:/app/libs/*" \
  bathe.BatheBooter \
  -Rio.featurehub.party.Application \
  -P/etc/common-config/common.properties \
  -P/etc/app-config/application.properties \
  -P/etc/app-config/secrets.properties
```

Sources: [docker/entrypoint.sh:1-44]()

---

## CI/CD Pipeline

Every push to the `main` branch triggers the `dkcicd` pipeline `build-and-push-image-v1`, which builds the image on `amd64` and pushes it to AWS ECR:

```yaml
build:
  provider: dkcicd
  pipelines:
  - name: build-and-push-image-v1
    parameters:
      awsAccountId: "053131491888"
      awsRegion: us-east-1
      imageRepo: dk-flags-party-server
      imageTag: 0.0.1
      dockerfilePath: "./docker/Dockerfile"
    runtime:
      architecture: amd64
    when:
    - event: push
      source: branch
      regex: ^(main)$
```

Sources: [.vtex/deployment.yaml:1-21]()

| Property | Value |
|---|---|
| CI provider | `dkcicd` |
| Pipeline | `build-and-push-image-v1` |
| Trigger | push to `main` |
| AWS account | `053131491888` |
| AWS region | `us-east-1` |
| ECR repository | `dk-flags-party-server` |
| Image tag | `0.0.1` |
| Build architecture | `amd64` |

---

## Production Endpoint

The published image runs behind **https://flags.vtex.com/**, which is the canonical FeatureHub Management UI and API endpoint for DK services. The deployment topology — load balancer, TLS, persistent storage, secret injection, scaling, backups, and DNS — is owned outside this repository. This repo contracts only the local image artifact.

Sources: [specs/dk-flags-party-server-image.md:11-12](), [specs/dk-flags-party-server-image.md:323-326]()

---

## Backstage Catalog Registration

The component is registered in the VTEX Backstage/service catalog via `.vtex/catalog-info.yaml`:

| Field | Value |
|---|---|
| Component name | `dk-flags` |
| Application ID | `OG35ZXTZ` |
| Owner team | `te-0034` |
| Lifecycle | `stable` |
| Type | `fullstack-app` |
| System | `dk-flags` |
| GitHub slug | `vtex/dk-flags` |
| TechDocs ref | `dir:../` |

Sources: [.vtex/catalog-info.yaml:1-22]()

---

## Downstream OpenFeature SDK Consumers

The image exposes the standard FeatureHub Management API. DK services connect using OpenFeature provider SDKs maintained in separate repositories:

| SDK | Target | Repository |
|---|---|---|
| JS client-side provider | Frontend | `vtex/featurehub-openfeature-client-provider-js` |
| JS server-side provider | Backend (Node) | `vtex/featurehub-openfeature-provider-js` |
| Go provider | Backend (Go) | `vtex/featurehub-openfeature-provider-go` |
| .NET provider | Backend (.NET) | `vtex/featurehub-openfeature-provider-dotnet` |

These SDKs connect to the running FeatureHub instance at the `APP_URL` (or `https://flags.vtex.com/` in production) to read feature flag state. The image does not bundle or distribute these SDKs; they are separate artifacts.

Sources: [README.md:9-13](), [specs/dk-flags-party-server-image.md:327]()

---

## Extension Points

### Adding or changing seed flags

Edit `docker/exampleFlags.json` and rebuild:

```bash
docker build -f docker/Dockerfile -t dk-flags-party-server .
```

Each entry in the file follows this schema:

```json
{
  "key": "my-feature-flag",
  "type": "BOOLEAN | STRING | NUMBER | JSON",
  "defaultValue": true,
  "name": "Optional display name",
  "description": "Optional description"
}
```

A `409` response from feature creation is treated as "already exists" and counted as `skipped` (not an error), making the setup idempotent across rebuilds.

Sources: [specs/dk-flags-party-server-image.md:241-253](), [specs/dk-flags-party-server-image.md:206-209]()

### Overriding admin credentials

Pass `ADMIN_USER`, `ADMIN_PASSWORD`, `ADMIN_EMAIL`, and optionally `APP_URL` as `--build-arg` or `-e` flags at `docker build` time. These are consumed by `build-setup.sh` and `setup.js` before the image layer is committed. The resulting image embeds those values immutably.

---

## Known Risks

| Risk | Impact | Notes |
|---|---|---|
| Floating base image tag (`featurehub/party-server:latest`) | High | A breaking API change in the base image will silently break the build-time setup. Pin to a specific FeatureHub version for stability. |
| Mutable ECR tag `0.0.1` | Medium | Every push to `main` overwrites the same tag. Consumers who reference `0.0.1` will silently pick up a new image on next pull. Pin by digest for stability-sensitive environments. |
| Admin credentials baked at build time | High (security) | Default `admin`/`admin` is intentional for dev/test. Production builds must override all `ADMIN_*` env vars at build time. Once baked, credentials cannot be rotated without a rebuild. |
| Run-time env var override confusion | Medium | `ADMIN_*` passed only at `docker run` will not rewrite the embedded admin. The README documents this, but the mismatch is a common operator confusion point. |
| Hard-coded `PORTFOLIO_NAME`, `ORGANIZATION_NAME`, `APPLICATION_NAME` | Medium | All three are constants in `config.js`, not env-overridable. Images for non-`integrationTest` topologies require a code change. |

Sources: [specs/dk-flags-party-server-image.md:168-177](), [docker/config.js:7-9]()

---

## Outstanding Follow-Ups

The spec identifies four open items that are not yet addressed in the current implementation:

1. **Reconcile `package.json#main`**: The `main` field points to `setup-api.js` and the `scripts.setup` field runs `node setup-api.js`, but the image build runs `setup.js` (the modular rewrite). These should be aligned.
2. **Make `PORTFOLIO_NAME`, `ORGANIZATION_NAME`, `APPLICATION_NAME` env-overridable**: Currently hard-coded in `config.js`; consumers with a different topology cannot customize them without a code change.
3. **Pin the base image tag**: Replace `featurehub/party-server:latest` with a versioned tag once a target FeatureHub version is selected.
4. **Evaluate `docker/run-setup.sh`**: This script provides a manual re-run path for setup but is not invoked by the image build. Its role should be clarified or the file removed.

Sources: [specs/dk-flags-party-server-image.md:226-234](), [docker/package.json:4-7]()

---

The `dk-flags-party-server` image encapsulates a fully-initialized FeatureHub instance in a single artifact, publishing to ECR on every merge to `main` and backing the production endpoint at `https://flags.vtex.com/`. The primary operational levers are the four `ADMIN_*` / `APP_URL` env vars at build time and the `docker/exampleFlags.json` flag seed file; everything else is hardwired and requires a code change or rebuild to modify.

---