Representative Environment Design

August 23, 2026 ยท View on GitHub

base-demo is the compact representative environment for Base-managed projects.

It should sit between a toy sample and Banyan Labs. It borrows the shape of a medium-sized engineering organization, but keeps each service intentionally small so the focus stays on Base orchestration, build tools, runtime diversity, and operational workflow.

Product Boundary

The three repos have distinct roles:

RepoRole
baseWorkspace and tooling control plane. Base owns setup, activation, project commands, checks, build/test delegation, and repo workflow support.
base-demoReduced-scale representative environment. It shows how Base manages a credible multi-language, infrastructure-backed project without deep product behavior.
banyanlabsFull platform engineering lab. It is where product behavior, operational depth, observability, delivery, Kubernetes, IaC, and cloud patterns earn their complexity.

base-demo should build confidence for Banyan Labs without becoming Banyan Labs. Its services should be boring on purpose: health checks, hello responses, metadata, documented ports, and simple build/test paths.

Future Go/Cobra CLI Boundary

A non-trivial Go/Cobra CLI does not belong in the current baseline demo. The baseline already includes a tiny Go HTTP API so Base can demonstrate Go build and test orchestration without adding another required tool shape to first-run setup.

If Base needs a deeper CLI example later, prefer a separate base-demo-go repository or a clearly optional advanced slice that is not required by BASE_DEMO_ENV=baseline, Quick Start, or read-only CI. Any later implementation should use Go's standard toolchain and Cobra only when command structure is complex enough to justify it.

Target Stack

The target stack is broad enough to feel real and small enough to inspect:

AreaRepresentative choiceIntent
PythonTiny Python HTTP API on port 8020 plus the existing Python CLIDemonstrate Python as both project command and app service runtime.
GoTiny Go HTTP API on port 8010Demonstrate Go service development and native test/build flow.
DockerDockerized Go service through ComposeMake Docker first-class without containerizing every app service.
JavaOne Gradle service on port 8030 and one Maven service on port 8040Demonstrate common Java build tools without Spring-scale complexity.
CTiny native process fixtureRepresent lower-level compiled components.
C++Tiny native process fixtureRepresent C++ service/tooling presence in a mixed environment.
JavaScript UIReact + Vite demo console on port 8070Provide a common frontend framework and human-facing operational surface.
DatabasesPostgres and MySQL through ComposeRepresent common data dependencies without cross-service dependency complexity.
CacheRedis through ComposeRepresent cache infrastructure as a local dependency pattern.

Each app service should expose the same small HTTP surface when practical:

  • /healthz
  • /hello
  • /info

The point is not business logic. The point is that Base can see, run, test, build, and explain a realistic mix of tools.

Services Command

The main operator surface should be one manifest command:

basectl run base-demo services -- status
basectl run base-demo services -- start
basectl run base-demo services -- stop
basectl run base-demo services -- restart
basectl run base-demo services -- check
basectl run base-demo services -- logs

The command should read a catalog rather than hard-code each service in the script. The catalog should be the source of truth for service name, kind, runtime/tooling, port, health URL, start/stop behavior, and whether the service is required for a given environment.

For process-backed entries, start waits for child survival and the configured health check within a bounded timeout before reporting success. Its state file records the PID, process-group ID, operating-system start time, and configured command. Later status, check, and stop operations require that identity to match; stop refuses to signal a live PID when it does not.

The status view should answer the practical local questions:

  • what is supposed to exist
  • what is running
  • where it is listening
  • what runtime or tool it represents
  • how health is checked
  • where logs live, or how to find them
  • when available, since when it has been running

Infrastructure Scope

Postgres, MySQL, and Redis should be representative dependencies, not a cross-service architecture exercise.

Only one or two later services may demonstrate a tiny database or cache probe if that makes the infrastructure visible. Most services should remain independent health/hello/info fixtures. This keeps the environment credible while avoiding a fake distributed-system dependency graph.

Compose should manage local infrastructure. The language build tools should still remain visible through native build and test commands.

The local infrastructure layer is:

NameKindPortScope
postgresDatabase5432Local dev, representative dependency
mysqlDatabase3306Local dev, representative dependency
redisCache6379Local dev, representative dependency

These dependencies are optional for services check until they are started. That keeps the default validation path stable on machines without Docker while still giving the main demo a real Compose-backed infrastructure surface.

Every published Compose port binds explicitly to 127.0.0.1. The checked-in database passwords and unauthenticated Redis instance are disposable local-demo settings; they are not a secret-management example and must not be reused for a shared or remotely reachable environment. Compose owns container names, while bin/base-demo-services derives its project name from the resolved checkout path and active environment. This lets worktrees address distinct projects and keeps lifecycle and log commands scoped to the invoking checkout. Automation can provide a valid lowercase project name through BASE_DEMO_COMPOSE_PROJECT. Fixed host ports still mean only one checkout can bind each port concurrently.

The upstream database and cache images use narrow mutable development tags so the same file remains portable across supported architectures. Dependency maintenance should explicitly pull, review release notes, render docker compose -f infra/compose.yaml config, and run the full suite. Immutable digests and image provenance belong to a production deployment contract, outside this demo's scope.

The first Dockerized application fixture is services/go-api. It exposes /healthz, /hello, and /info on port 8010 and is registered in both services/catalog.json and infra/compose.yaml.

The first native process fixture is services/python-api. It exposes the same endpoint shape on port 8020 using only Python's standard library and is managed by the services command through a small process lifecycle entry in the catalog.

The Java fixtures are intentionally split between services/java-gradle-api and services/java-maven-api. The services both use the JDK built-in HTTP server and expose the same endpoint shape; the distinction is the build tool, because both Gradle and Maven are common enough to be first-class in a representative IT environment.

The native fixtures are services/c-service and services/cpp-service. They use small Makefiles and process-state health checks to avoid adding C/C++ HTTP frameworks solely for the demo. They do not bind or advertise network ports. Their standalone /healthz, /hello, and /info command behavior remains available for build tests, while service health requires an identity-matched process started by the lifecycle command.

The UI fixture is services/demo-console, a React/Vite operational console that reads the checked-in service catalog copy generated from services/catalog.json. It is deliberately a dashboard surface, not a product app; the catalog remains the source of truth.

Environment Model

base-demo should model three environments:

environments/
  dev.json
  staging.json
  prod.json

Only dev is operational by default. staging and prod are checked-in configuration examples. They change the modeled URL and logging settings, keep only project-baseline selected, and disable the same Postgres, MySQL, and Redis definitions and ports used by dev. They do not declare separate image tags, database/cache resource names, or deployment credentials.

The services command loads these files through the same validator as the environments command. It filters the catalog using the selected environment, applies requiredness and enabled-infrastructure overrides consistently, and refuses start, stop, or restart when operational is false. Modeled environments remain available to status, check, and logs for inspection, but they are never treated as deployable targets.

The validator enforces the exact top-level fields, HTTP(S) base_url values without embedded credentials, supported logging levels and formats, boolean service requiredness, boolean infrastructure enablement, ports in the 1..65535 range, and references to the service catalog and Compose file. Errors include the failing nested path, such as services.python-api.required or infrastructure.postgres.port.

BASE_DEMO_ENV=baseline and services --env dev are intentionally different contracts. The former is a Base manifest health marker set by activation and CI; it proves health.required_env. The latter selects the representative service configuration and defaults to the only operational model, dev.

The files use JSON so the demo can validate them with Python's standard library instead of adding a YAML dependency solely for configuration parsing.

This is deliberate. A real deployable staging/prod story belongs in Banyan Labs. base-demo should teach the shape of environment-aware configuration without requiring cloud accounts, Kubernetes, Terraform, or secret management.

Main Demo Shape

The representative stack should be part of the main demo, not hidden behind an advanced path.

The walkthrough shows:

  1. Base project discovery and setup.
  2. Manifest-declared commands.
  3. Environment configuration.
  4. Service catalog status.
  5. Build/test delegation across runtimes.
  6. Service catalog checks across optional local dependencies and fixtures.
  7. Local infrastructure and service startup through a dry-run lifecycle path.
  8. React/Vite console registration as the human-facing view.
  9. Build-target discovery for every representative service.

The default validation path should remain stable. Heavy checks can be skipped with a clear message when Docker or a language toolchain is unavailable, but the repo shape and command contracts should always be validated.

CI runs the representative BATS suites, validates all environment files, checks the service catalog through Base, and exercises service startup with BASE_DEMO_SERVICES_DRY_RUN=1. That gives the main demo and CI the same operator surface without requiring every dependency to stay running during a baseline validation run.

Implementation Train

The implementation moved one issue at a time:

IssueSlice
#62Define and publish this representative environment direction.
#63Add the service catalog and services lifecycle command.
#64Add the dev, staging, and prod environment model.
#65Add Compose-backed Postgres, MySQL, and Redis.
#66Add the Go API service and Docker image fixture.
#67Add the Python API service fixture.
#68Add Java Gradle and Maven service fixtures.
#69Add C and C++ service fixtures.
#70Add the React/Vite service console UI.
#71Integrate the representative environment into demo validation and CI.

Each PR should keep the demo runnable, update docs when command behavior changes, and preserve the issue-first workflow from AGENTS.md.