"Chart one software system as a C4 container diagram: deployables identified by project output, host builder, Dockerfile, or process manifest, the stores and brokers they bind in committed configuration, and the modules contained in one deployable. Use when: 'map containers', 'container diagram', 'what actually runs', 'deployables and databases', 'modular monolith', 'which services bind to which broker'. Skip when: the question is which repositories exist (map-landscape), what is inside one deployable (map-components), or environment topology."
Resources
2Install
npx skillscat add melodic-software/claude-code-plugins/plugins-architecture-skills-map-containers Install via the SkillsCat registry.
Repository context
The current repository is both the CONSUMER, whose convention home declares where artifacts land,
and the DEFAULT SUBJECT, the repository whose committed files name deployables and stores.
Collect with an individual Bash call, one command per call: the project root,git rev-parse --show-toplevel. Treat a failure (not a repository, git unavailable) as an unknown
value and carry on; ${CLAUDE_PROJECT_DIR} is the resolver's --root either way.
An optional [system] argument is only a focal label. It does not select a subdirectory and it
does not walk for nested repositories. Pass it to the collector as --focal. When it is absent,
the focal name is the github.com origin repository name, otherwise the directory basename.
Purpose
Answer "what actually runs, and which stores does it bind" from committed project files, Dockerfiles,
process manifests, and configuration. Every deployable cites the fact that made it a container.
Every store cites a config key. The scripts collect and render. Do not draw a node the script did
not emit, and do not turn a directory name into a service.
This is the C4 container rung. map-landscape is the rung above it. What is inside one deployable
is /architecture:map-components. A request trace is /architecture:map-flow.
Resolve home and dialect
Read ${CLAUDE_PLUGIN_ROOT}/reference/config.md first. This skill writes into architecture_dir.
It does not read landscape_dialect and it does not add a dialect key. The diagram dialect isdiagram_dialect.system from the authoring-formats topic doc.
Run bash "${CLAUDE_PLUGIN_ROOT}/lib/resolve-convention-home.sh" --root "${CLAUDE_PROJECT_DIR}" and
follow the exit code. Never parse the root instruction file yourself. Exit 0 means read<home>/architecture/README.md for architecture_dir. Exit 1, 2, and 3 mean there is no declared
home to read.
In order: --out <dir> wins for this run alone, then a declared architecture_dir, then one
question. architecture_dir has NO default. An undeclared and unconfirmed home, including every
non-interactive run, STOPS and points at /architecture:setup. Do not invent a directory.
Resolve diagram_dialect.system by restating this ladder, then running the resolver rather than
parsing the topic doc yourself. The ladder is a resolution order, not a task list:
1. Anchor at the repository root: `${CLAUDE_PROJECT_DIR}` when set, otherwise
`git rev-parse --show-toplevel`. Never a CWD-relative read.
2. Resolve the convention home `<home>` with the bundled resolver above. Never hand-parse the root
file.
3. The printed home is repo-relative: join it to the root, then pass
`<root>/<home>/authoring-formats/README.md` to the resolver.
4. Layer order is one layer deep: an explicit `--dialect` argument, then the team convention doc.
There is no personal overlay.
5. `diagram_dialect.system` has NO default. Allowed values are `likec4` and `c4-plantuml`. When it
is unset, write `containers.json` and `containers.md` with the tables, and draw no diagram block.
6. Degrade soft, and say so. No pointer, no doc, no key, or an unrecognized value (mermaid
included, which the convention refuses) each resolve to emitting no view. The resolver names
the cause on stderr. Do not hard-fail and do not ask the operator to create the surface
mid-task.
7. Report provenance: the key, the value, and the layer (`argument`, `team convention doc <path>`,
or `unset (no C4 view emitted)`).bash "${CLAUDE_PLUGIN_ROOT}/lib/resolve-diagram-dialect.sh" --kind system \
--formats "<root>/<home>/authoring-formats/README.md"Omit --formats when no convention home resolved. Stdout is likec4, c4-plantuml, or none.
An explicit --dialect likec4|c4-plantuml on the invocation wins and the resolver is not required.
This skill never writes the consumer's root instruction file or its topic doc. /architecture:setup apply owns both.
Build the record
When <architecture_dir>/dependency-graph.json already exists and is schema_version 1, pass it as--graph. Containment then comes from that graph's project edges and is not re-derived. Otherwise
omit --graph. The collector reads ProjectReference Include attributes itself and recordscontainment as project-references.
"${CLAUDE_SKILL_DIR}/scripts/collect-containers.sh" \
--repo "<subject-repo>" --out "<architecture_dir>/containers.json" \
--focal "<system-or-omit>" --graph "<dependency-graph.json-or-omit>"The ${CLAUDE_SKILL_DIR} anchor matters. A bare relative path resolves against the session's working
directory, which is not where the script lives.
The record is schema_version 1 in the one-object-per-line layout the script writes. Container lines
start with {"id":. Module lines start with {"container":. Edge lines start with {"from":.
A deployable kind is web, api, worker, cli, function, or process. A store kind isstore. technology is a runtime, framework, or image, or the literal unknown. An edge kind
is uses or shared-infrastructure. The shared-infrastructure evidence cites every config key.
Redaction is ${CLAUDE_PLUGIN_ROOT}/lib/redact-connection.sh (the awk beside it). A password,
token, account key, or URL userinfo must not appear in the record, the diagram, or stdout.
The collector matches committed files. It does not execute them. A class library is a contained
module, not a container, even when its directory name sounds like a service.
Render
"${CLAUDE_SKILL_DIR}/scripts/render-containers.sh" \
--record "<architecture_dir>/containers.json" --out "<architecture_dir>" \
--dialect "<likec4|c4-plantuml|none>"Write containers.json first, then render from it. c4-plantuml writes containers.md with one
fenced plantuml block (C4_Container: a System_Boundary holding Container, ContainerDb, andContainerQueue elements). likec4 writes containers.md with one fenced likec4 block (the same
elements nested in the software system, and a container view). none writes containers.md
with the tables and no diagram block. Contained modules are named on their deployable. They are
not drawn as containers.
The script prints one summary line on stdout:containers: focal=<name> deployables=<n> stores=<n> modules=<n> edges=<n> shared=<n> unknown_technology=<n> thin=<yes|no> dialect=<likec4|c4-plantuml|none>.
Keep it for the report. A result is thin when deployables=0.
Exit 1 means the record is unreadable, not schema_version 1, or not in the one-object-per-line
layout. Nothing was written. Report that message. Do not reformat the record by hand and do not
treat a layout failure as an empty diagram.
Close with the report
End every run with this block, in this order, filled from the record and the script exits:
- Artifacts: each path written, or
none writtenwhen the run stopped before a home existed. - Focal: the system name, and that it is the repository being charted.
- Deployables: the summary's
deployables=count, and that kind came from output type, host
builder, Dockerfile, or process manifest. - Stores: the summary's
stores=count. Every store cites a file and a config key. - Modules: the summary's
modules=count. A modular monolith is one container. - Shared: the summary's
shared=count. Each shared-infrastructure edge cites every config key. - Dialect:
diagram_dialect.system, its value, and the layer:argument,team convention doc <path>, orunset (no C4 view emitted). - Technology:
unknown_technology=from the summary.unknownis literal, not a guess. - Containment source:
dependency-graph.jsonorproject-references. - Thin result:
no, oryesbecause no deployable was found. Name/architecture:map-landscape
when the question was which repositories exist. - Redaction: the record keeps host and service kind. It does not keep the raw value.
What this skill does NOT do
- Draw environment topology, replicas, or scaling. That is
/architecture:map-deployment. - Chart the modules inside one deployable as their own diagram. That is
/architecture:map-components. - Trace a request. That is
/architecture:map-flow. - Add a dialect key, read
landscape_dialect, or draw mermaid C4. - Execute configuration, fetch anything, or edit a project file. The only writes are
containers.jsonandcontainers.mdunder the resolved output directory. - Invent a home. No declared, no
--out, and no confirmedarchitecture_diris a stop, not a
default. - Treat two projects in one repository as an edge. An edge needs a cited config key or a configured
endpoint.
Next
- What is inside one deployable:
/architecture:map-components. - A request trace from one entry point:
/architecture:map-flow. - Neighboring repositories need a landscape:
/architecture:map-landscape. - The view settles a decision worth keeping:
/architecture:record-decision.
Gotchas
- A container diagram is one software system. The primary elements are the containers inside
that system. Deployment concerns such as clustering and failover are not this diagram. Verified
2026-09-28 against https://c4model.com/diagrams/container and https://c4model.com/. Recheck
when the container-diagram page changes its scope, its primary elements, or moves deployment
concerns onto this diagram. - Directory names are not deployables.
Microsoft.NET.Sdk.Webin a directory named Worker is a
web host. A class library in a directory named Api is a module. Host-builder usage is read only
for a project that is already an entry point (web SDK, worker SDK, functions, orOutputTypeExe). - A modular monolith is one container. Libraries reached by project references are listed as
contained modules. They are not drawn as containers. Whendependency-graph.jsonis passed, those
edges are the only containment. AProjectReferencethe graph does not have is not a module. - Shared infrastructure cites both keys. Two deployables that name the same broker host produce
oneshared-infrastructureedge whose evidence lists every config key. Sitting in the same
repository does not. - Redaction keeps the shape. Host and service kind are the fact. Userinfo, passwords, account
keys, and secret-only values produce no field. The raw value is not stored.package.jsonis not
scanned.containers.jsonis not scanned again. unknownis a technology value. A Dockerfile with noFROM, or a compose service with no
image, is technologyunknown. Do not invent a runtime from the service name.- The dialect key is
diagram_dialect.system, and it has no default. The operator's decision
on #4639 puts every C4 view of the code on the key the authoring-formats convention assigns to
C4 system views, the same key/planning:designreads for its design container view. It refuses
mermaid because mermaid C4 is experimental. Unset,containers.mdcarries the tables and no diagram,
and the report says no view was emitted. The decision is recorded in${CLAUDE_PLUGIN_ROOT}/reference/config.md. - A reformatted record reads as empty unless the reader refuses it.
render-containers.sh
exits 1 on any other shape and writes nothing. - Configuration is untrusted text. The assignment scanner matches it. It does not source it,
eval it, or interpolate it into a command. - A quote in repository-controlled text is replaced, not preserved. A name lands inside a
quoted diagram literal. The delimiter is swapped for one that cannot close the literal.