Retrofit Overlays
The *-service-empty-archetype tier is not a service generator. It is a platform overlay: run it against an existing repository to retrofit that repository with the platform's servicing layer, generating no project scaffolding and no domain code.
archetect render git@github.com:p6m-archetypes/golang-service-empty-archetype.git#dev /path/to/existing-project
There is one per language, and they are the tier to reach for when onboarding a legacy application:
dotnet-service-empty-archetype · golang-service-empty-archetype · java-service-empty-archetype · python-service-empty-archetype · rust-service-empty-archetype · typescript-service-empty-archetype
The Workflows Are Identical
Every empty archetype composes the same <lang>-ci-library as its full service sibling, with the same call and no options:
local ci = require("golang-ci")
ci.render(context, dest)
So the generated build.yaml and cut-tag.yaml are byte-for-byte what CI/CD Workflows documents - same triggers, same permissions, same twelve-step chain, same shared steps, same secrets and variables, same CD handoff, and the same absence of a promote.yaml. Read the per-language page for your language; none of it changes here.
What changes is the context the pipeline runs in. Four differences matter.
1. Everything Renders in Place
A full service archetype renders into a project-name/ subdirectory. An overlay passes an empty destination, so every file lands at the destination root:
| Tier | Destination |
|---|---|
| Full service archetype | local dest = { destination = context:get("project-name") } |
| Empty overlay | local dest = {} - the repository root you pointed it at |
That is what makes it a retrofit rather than a sibling checkout.
2. The Container Build Is Parameterized, Not Assumed
This is the substantive CI/CD difference. build.yaml still hardcodes dockerfile-path: .platform/docker/prd/Dockerfile, but the overlay's version of that Dockerfile cannot assume a module name, a crate, a src/ layout, or an output path - it is retrofitting somebody else's repository. So it copies the whole repo and runs the application's own build command, supplied by two extra prompts the full archetypes do not have:
| Prompt | Key | Feeds |
|---|---|---|
| Build Command | build_command | The RUN line in the builder stage |
| Runtime Artifact | runtime_artifact | The path the runtime stage copies out and executes |
The rendered Go Dockerfile, for example:
FROM golang:1.23 AS builder
WORKDIR /app
COPY . .
RUN go mod download
RUN {{ build_command }}
FROM gcr.io/distroless/static:nonroot
COPY --from=builder /app/{{ runtime_artifact }} /server
EXPOSE {{ service_port }}
EXPOSE {{ management_port }}
ENTRYPOINT ["/server"]
The defaults reproduce what each language's own service archetype produces, so a greenfield-shaped repository needs no answer at all. A legacy repository overrides one line instead of rewriting a Dockerfile:
| Language | build_command default | runtime_artifact default |
|---|---|---|
| .NET | dotnet publish -c Release -o /app/publish | <ProjectName>.dll |
| Go | CGO_ENABLED=0 GOOS=linux go build -trimpath -ldflags="-s -w" -o server ./cmd/server | server |
| Java | mvn -q -B -DskipTests package | target/*.jar |
| Python | uv pip install --system --no-cache . | <project-name> (console script on PATH) |
| Rust | cargo build --release | <project-name> (binary in target/release/) |
| TypeScript | pnpm build | dist/index.js |
The Java default is -DskipTests, because the Build step has already run mvn verify against the same commit. The container build packages; it does not re-test.
3. Nothing Is Overwritten
The overlay archetypes document, and their shared acceptance suite holds, that a retrofit leaves the application's own files alone - archetect never overwrites an existing path.
If the repository you are retrofitting already has a .github/workflows/build.yaml, the overlay's build workflow is not written. You keep the legacy pipeline and silently get no platform CI - no digest, no manifest dispatch, no deployment. Check .github/workflows/ after rendering, and if the platform workflow is absent, reconcile the two by hand.
The same applies to .editorconfig, .gitattributes, .gitignore, and the Dockerfiles.
4. The Guarded Build Steps Now Read the Legacy Repository
Several steps in the shared pipeline are guarded on files existing, and the per-language pages describe how those guards resolve for a freshly generated project. On a retrofit they resolve against whatever the legacy repository already contains, which flips some conclusions:
| Language | Guarded behavior | Greenfield | On a retrofit |
|---|---|---|---|
| Python | uv run ruff check needs [tool.ruff] in pyproject.toml | Skipped | Live gate if the project already configures Ruff |
| Python | uv run pytest needs [tool.pytest] or test_*.py | Runs | Runs if the project has tests |
| TypeScript | pnpm lint needs a lint script | Skipped | Live gate if the project defines one |
| TypeScript | pnpm test / pnpm build need those scripts | Run | Skipped if absent - a missing test script silently disables testing |
| Java | mvn checkstyle:check needs the plugin in pom.xml | Skipped | Live gate if the project already uses Checkstyle |
| Go | install-protoc: auto and codegen key off proto/ and gqlgen.yml | Per protocol | Activate on the project's existing files |
| Go | Toolchain version read from go.mod | Archetype's | The legacy go.mod |
| Rust | cargo fmt --check and clippy -D warnings are unguarded | Hard gates | Hard gates against legacy code from the first run |
| .NET | dotnet-setup defaults to SDK 8.0.x | Matches | Does not adapt - pin dotnet-version or add a global.json |
The two rows worth planning around before you open the PR:
- Rust has no escape hatch.
cargo fmt --checkandcargo clippy -- -D warningsrun on every branch with no existence guard, so a legacy Rust codebase that is not already clean fails its first CI run outright. Either clean it or overriderun-format-check/run-lintin the renderedbuild.yaml. - .NET silently builds with SDK 8.0.x regardless of what the project targets. Add a
global.json, or passdotnet-versionto bothdotnet-setupanddotnet-build.
What Else the Overlay Renders
| Path | Notes |
|---|---|
.github/workflows/build.yaml, cut-tag.yaml | From <lang>-ci-library - identical to the full tier |
.platform/kubernetes/** | PlatformApplication CRD plus dev/stg/prd overlays, including resourceRequirements |
.platform/docker/prd/Dockerfile | The image CI publishes, parameterized as above |
.platform/docker/local/Dockerfile | Local development image |
Tiltfile | Go and Rust only. The .NET, Java, Python and TypeScript overlays do not render one |
.editorconfig, .gitattributes, .gitignore | Only where the repository does not already have them |
Prompt Surface
Smaller than the full tier: no author identity, no organization/solution split, no project prefix or suffix, because those exist to name code the overlay does not generate.
| Prompt | Consumed by |
|---|---|
| Application Name | IMAGE_NAME and APPLICATION_NAME in build.yaml, the PlatformApplication name, and the kubernetes/<dir>/ directory CD writes into |
| Solution Slug | The image path, and the {solution}-{application}-{env} namespace |
| Image Registry | The image path |
| Protocol | Whether the manifests inject SERVER_PORT or GRPC_PORT |
| Service / Management Port | Published ports, the container EXPOSE lines, the readiness probe |
| Build Command / Runtime Artifact | The production Dockerfile |
| Persistence / Cache / Messaging | The manifests' resourceRequirements only - no connection code is woven |
| Source Control | The optional publish-the-repository step |
Only the first three have no default, so a headless render needs just:
archetect render <source> /path/to/existing-project --headless \
-a project_name=billing-service \
-a org_solution_name=acme-payments \
-a image_registry=ghcr.io
<application>_dbA full service archetype names the platform-provisioned database from its identity prompts. An overlay has none, so it names it after the application - unmistakably distinct from whatever database the legacy application may already carry.
Related
- CI/CD Workflows - the workflow files themselves, step by step
- Integrating Existing Applications - the wider onboarding path
- Assessing Readiness - evaluate before you retrofit
- Promotion - adding the
stgandprdpromotion the overlay does not generate - Containerization - Dockerfile patterns