Skip to content

Add container info command#1769

Open
muk2 wants to merge 7 commits into
apple:mainfrom
muk2:feat/815-container-info-command
Open

Add container info command#1769
muk2 wants to merge 7 commits into
apple:mainfrom
muk2:feat/815-container-info-command

Conversation

@muk2

@muk2 muk2 commented Jun 20, 2026

Copy link
Copy Markdown

Summary

Adds a top-level container info command — the analog of docker info — that
prints client, host, server, storage paths, resource counts, and default
settings in a table or as JSON (--format json). Daemon-sourced fields populate
only when the API server responds, so the command degrades gracefully to a
"server not running" view when the daemon is stopped.

Testing

  • make check passes (formatting + license headers).
  • Unit tests: SystemInfoTests (5 tests) cover table rendering with and without
    a running server, JSON round-tripping, and omission of optional fields. All pass.
  • Manual: container info with the daemon stopped shows client/host plus
    server: not running; after container system start, server/paths/counts
    populate; container info --format json | jq emits valid JSON.

Fixes #815

Implements issue apple#815: a top-level `container info` command (like
`docker info`) that reports system-wide information so tools can use
`container` as a drop-in via `ln -s container docker`.

The command gathers, from real APIs only:
- CLI version/build/commit (ReleaseVersion)
- Host architecture, OS, CPU count (Arch / ProcessInfo)
- API server status/version/build/commit and app/install/log roots
  (ClientHealthCheck)
- Container counts (total/running) and image count when the daemon is up
  (ContainerClient.list / ClientImage.list)
- Default registry domain, kernel binary path, container CPU/memory,
  builder image, init image, and DNS domain (ContainerSystemConfig)

Supports the standard `--format table|json|yaml|toml` output flag used by
other commands. Daemon-dependent sections are omitted gracefully when the
API server is not running.

Fixes apple#815
@stephenlclarke

Copy link
Copy Markdown

Nice addition, this looks like a useful docker info-style surface.

I spotted one small bug in the image count path. In SystemInfo.gather(), this block unwraps resources into a local shadow copy:

if var resources, let images = try? await ClientImage.list() {
    resources.images = images.count
}

That means resources.images is updated only on the copy. The InfoPayload returned from gather() still has images == nil, so images.total is omitted from table/JSON output even when ClientImage.list() succeeds.

I think this can be fixed with:

if let images = try? await ClientImage.list() {
    resources?.images = images.count
}

It may also be worth adding a small regression test around this path, either by making the gather dependencies injectable or by testing a focused helper, so the image count behavior is covered directly.

The image count was assigned to a shadowed copy of the resource counts
created by an `if var resources` binding, so it never reached the
`InfoPayload` returned by `gather()`. As a result `images.total` was
omitted from table and JSON output even when `ClientImage.list()`
succeeded.

Apply the count to the outer value via a small, pure `withImageCount`
helper and add regression tests covering the count path.
@stephenlclarke

Copy link
Copy Markdown

@muk2 Are you going to add the info command to the docs/command-reference.md

@jglogan

jglogan commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

@muk2 Thanks for the submission. I don't think I had seen the underlying issue before. We're not trying to do 100% compatibility with peripheral docker commands, and with the exception of build, the commands directly under container are for container resource management.

The closest equivalent in container to docker info is container system status. Could you have a look at it and let me know whether what you have here would work as an enhancement to that command?

@stephenlclarke

Copy link
Copy Markdown

@jglogan...

That makes sense to me. From the outside, the reusable part of this PR seems to be the richer system/status payload rather than the Docker-compatible top-level command name. Moving the useful fields under container system status --format json would give downstream tooling a stable Apple-native surface without implying full docker info compatibility.

More broadly, is Apple expecting downstream plug-in/tool developers to build against these Apple-native container system ... surfaces, rather than Docker-shaped compatibility commands? That guidance would be helpful for projects trying to integrate cleanly without pushing the core CLI toward full Docker parity.

@jglogan

jglogan commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

@stephenlclarke First, thanks for having a look at the PR and commenting on it!

Yes, it'd take a bit of searching but we've had several discussions on Docker CLI compatibility. Long story short, it's not an objective for this project, partly due to the UX goals when we were planning the initial release of the tool, partly due to winding up with container as the tool name (container container run would be a bit bizarre), but also that we didn't want to have to deal with the support burden of maintaining compatibility when there are many other aspects of the tool requiring effort.

We recognize that achieving CLI compatibility eventually in some way is really important. My preference is to do so by being able to use the Docker CLI itself with container. Even if we made container sufficiently CLI-compatible to support ecosystem tools (e.g. dev containers), we'd still have additional effort to implement SDK-level compatibility (for tools such as Testcontainers). So it seems more sensible to me for all of us to concentrate effort on an API bridge (that is distinct from the container tool but could possibly integrate as a service plugin) that supports both the CLI and the API ecosystem.

Regarding container info specifically - it's a pretty sparse result now; it would be useful to enhance the result with plugin information and anything else that might be useful for displaying system configuration and status information to the user and tools. I'm open to aligning the output more with that of docker info if it seems sensible and natural.

@stephenlclarke

Copy link
Copy Markdown

@jglogan That also helps with the direction I should take in container-compose. My earlier implementation started closer to a Compose-facing layer, then I explored pushing more behavior through direct container API calls and separating the Compose surface from the container surface. Based on this guidance, it sounds like the better boundary is to keep the Docker/Compose-shaped behavior in container-compose, and keep upstream apple/container contributions focused on Apple-native primitives, stable machine-readable surfaces, and extension points that would also help a future API bridge or service plugin.

If that matches your intent, I can use that as the organising principle for future small PRs.

@jglogan

jglogan commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

@muk2 Thanks for the update - it looks like you created a separate system info command. Do you think there's a need for a system info that's separate from the existing system status command?

Or could the information you're adding be cleanly integrated into system status without the need for a new command?

@jglogan

jglogan commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

it sounds like the better boundary is to keep the Docker/Compose-shaped behavior in container-compose, and keep upstream apple/container contributions focused on Apple-native primitives, stable machine-readable surfaces, and extension points that would also help a future API bridge or service plugin

Yes. That's pretty much exactly it. You can shell out to the container CLI, or equivalently, use the ContainerCommands equivalents as your "container client API", or use the lower level ContainerClientAPI. The latter will likely continue to require source changes for a while as the API evolves, but the goal is to ensure forward/backward compatibility on the underlying XPC protocol to avoid issues where 3rd party apps using the client API fail to work due to protocol mismatches.

An example of enhancements that would help a future API bridge: evolving the resource model so id is a system-assigned ID that's a 32-digit hex value like Docker, and name represents the user-assigned name. This would eliminate the need for a bridge to generate and persist Docker-like IDs for the bridged resources (and would allow us to implement a cross-cutting prefix matching capability for IDs which is really nice for usability).

With regard to Docker CLI compatibility, you can see that we stay very close to how Docker works for the things people use most and where it's a clean fit to do so. The option set for container create and run is pretty much that for Docker. However, there are cases where the fit isn't as great (multiplatform image handling in Docker is sort of bolted onto a single-platform image store model, whereas in container we had the luxury of building our image store long after multiplatform images became a thing).

Drop the separate top-level `container info` command and fold its richer
payload (client, host, resource counts, and system defaults) into
`container system status`. `system status` keeps its existing role and
exit-code contract: it still reports `unregistered`/`not running` and
exits non-zero in those cases, but when the daemon is up it now emits the
full client/host/server/paths/resources/defaults view in table or JSON.

This avoids a Docker-shaped command name and gives downstream tooling a
single Apple-native machine-readable surface, per PR review feedback.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@muk2

muk2 commented Jun 26, 2026

Copy link
Copy Markdown
Author

@jglogan Good call — there's no real need for a separate command here. I've removed the top-level info command and folded its richer payload into container system status (pushed in 8e2d08b).

system status keeps its existing role and contract: it still reports unregistered/not running and exits non-zero in those cases. When the daemon is up, it now emits the full view — client, host, server, paths, resource counts (containers/images), and system defaults — in either table or --format json. So there's a single Apple-native, machine-readable surface rather than two overlapping ones, which also lines up with the guidance above about stable surfaces for downstream tooling.

The image-count fix from the earlier review carried over, and the unit tests moved to SystemStatusTests (8 tests, all passing; make check is clean). Happy to extend the payload further (e.g. plugin info) in a follow-up as you suggested.

@jglogan

jglogan commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

@muk2 can you update the title and description for the PR, and in the description can you include examples of the new table and json format output. I've kicked off the PRB. Thanks for merging this into the system status command!

@jglogan

jglogan commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Seeing some errors in the tests:

◇ Test explicitTableFormat() started.
✘ Test jsonOutputValidStructure() recorded an issue at TestCLIStatus.swift:135:9: Expectation failed: an error was thrown when none was expected: "DecodingError.keyNotFound: Key 'appRoot' not found in keyed decoding container. Debug description: No value associated with key CodingKeys(stringValue: "appRoot", intValue: nil) ("appRoot")."
↳ // Should always produce valid JSON regardless of status
✘ Test jsonOutputValidStructure() recorded an issue at TestCLIStatus.swift:131:6: Caught error: DecodingError.keyNotFound: Key 'appRoot' not found in keyed decoding container. Debug description: No value associated with key CodingKeys(stringValue: "appRoot", intValue: nil) ("appRoot").
✘ Test jsonOutputValidStructure() failed after 0.114 seconds with 2 issues.
✔ Test explicitTableFormat() passed after 0.116 seconds.
✘ Test jsonFormat() recorded an issue at TestCLIStatus.swift:64:6: Caught error: DecodingError.keyNotFound: Key 'appRoot' not found in keyed decoding container. Debug description: No value associated with key CodingKeys(stringValue: "appRoot", intValue: nil) ("appRoot").
✘ Test jsonFormat() failed after 0.123 seconds with 1 issue.
✘ Test defaultDisplaysTable() recorded an issue at TestCLIStatus.swift:58:9: Expectation failed: (fullOutput → "FIELD                      VALUE
status                     running
client.version             1.0.0-23-g7b94575
client.build               debug
client.commit              7b945758ff1afbebd77506cff5637a44ebc55e88
host.os                    Version 26.5 (Build 25F71)
host.architecture          arm64
host.cpus                  8
server.version             container-apiserver version 1.0.0-23-g7b94575 (build: debug, commit: 7b94575)
server.build               debug
server.commit              7b945758ff1afbebd77506cff5637a44ebc55e88
server.appName             container-apiserver
paths.appRoot              /Users/runner/actions-runner/_work/_temp/tmp.KyNi7PTfi3/
paths.installRoot          /Users/runner/actions-runner/_work/container/container/
paths.logRoot              /Users/runner/actions-runner/_work/_temp/tmp.KyNi7PTfi3/logs
containers.total           0
containers.running         0
images.total               6
defaults.registryDomain    docker.io
defaults.kernelBinaryPath  opt/kata/share/kata-containers/vmlinux-6.18.15-186
defaults.containerCPUs     4
defaults.containerMemory   1gb
defaults.builderImage      ghcr.io/apple/container-builder-shim/builder:0.12.0
defaults.initImage         ghcr.io/apple/containerization/vminit:0.34.0").contains("apiserver.version")
✘ Test defaultDisplaysTable() recorded an issue at TestCLIStatus.swift:59:9: Expectation failed: (fullOutput → "FIELD                      VALUE
status                     running
client.version             1.0.0-23-g7b94575
client.build               debug
client.commit              7b945758ff1afbebd77506cff5637a44ebc55e88
host.os                    Version 26.5 (Build 25F71)
host.architecture          arm64
host.cpus                  8
server.version             container-apiserver version 1.0.0-23-g7b94575 (build: debug, commit: 7b94575)
server.build               debug
server.commit              7b945758ff1afbebd77506cff5637a44ebc55e88
server.appName             container-apiserver
paths.appRoot              /Users/runner/actions-runner/_work/_temp/tmp.KyNi7PTfi3/
paths.installRoot          /Users/runner/actions-runner/_work/container/container/
paths.logRoot              /Users/runner/actions-runner/_work/_temp/tmp.KyNi7PTfi3/logs
containers.total           0
containers.running         0
images.total               6
defaults.registryDomain    docker.io
defaults.kernelBinaryPath  opt/kata/share/kata-containers/vmlinux-6.18.15-186
defaults.containerCPUs     4
defaults.containerMemory   1gb
defaults.builderImage      ghcr.io/apple/container-builder-shim/builder:0.12.0
defaults.initImage         ghcr.io/apple/containerization/vminit:0.34.0").contains("apiserver.commit")

@jglogan

jglogan commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

@muk2 One other thing that we should fix while we're here!

server.version             container-apiserver version 1.0.0-23-g7b94575 (build: debug, commit: 7b94575)

I had noticed this some time back - this shouldn't contain a composition of all the other fields - I think we should just report 1.0.0-23-g7b94575 for this value.

Update TestCLIStatus to the nested `system status` payload (paths.*/server.*)
so it decodes the running and not-running cases, fixing the `appRoot`
keyNotFound failures and the `apiserver.version`/`apiserver.commit` table
assertions.

Report the bare release version for `apiServerVersion` in the health check
reply instead of the composed `singleLine` string, so `server.version`
(and `system version`) show e.g. `1.0.0-23-g7b94575` rather than a
composition of the build/commit fields that are already reported separately.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@muk2

muk2 commented Jun 30, 2026

Copy link
Copy Markdown
Author

@jglogan Thanks — both are addressed in 3f46827.

Failing tests: TestCLIStatus was still asserting against the old flat
system info schema (appRoot at the top level, apiserver.version/
apiserver.commit in the table), which is why it hit the appRoot
keyNotFound decode error after the payload moved under system status. The
test now decodes the nested payload — paths.{appRoot,installRoot,logRoot} and
server.{version,build,commit,appName} — with the daemon-sourced sections
optional so the running and not running/unregistered cases both decode
cleanly. Table assertions updated to paths.appRoot / server.version /
server.commit accordingly.

server.version composition: good catch. The value came from the health
check reply setting apiServerVersion to ReleaseVersion.singleLine(...),
which folds in build + commit. Since those are already reported as their own
fields, I changed the harness to send just ReleaseVersion.version(), so
server.version (and the server row of container system version) now reports
e.g. 1.0.0-23-g7b94575. This also makes the server row consistent with the
client row, which already used the bare version.

Verified locally: swift build --build-tests is clean, SystemStatusTests
(8) and TestCLIStatus (6) pass, and make check is clean. I don't have a
registered daemon in this environment so the running-state branch of the CLI
tests skips here, but it should exercise fully in the PRB.

muk2 and others added 2 commits June 30, 2026 17:28
stephenlclarke added a commit to stephenlclarke/container-compose that referenced this pull request Jul 12, 2026
container compose top now renders Docker Compose-style per-container process tables for selected services with UID, PID, PPID, C, STIME, TTY, TIME, and CMD columns.

The compose layer uses the generic ContainerClient.processes(id:) API and keeps the PID-only fallback for older or mixed matched-runtime lanes.

Live runtime parity now configures CONTAINER_COMPOSE_INIT_IMAGE, installs a matching source-built vminit image through CONTAINER_INIT_IMAGE_NAME, and pins the release stack to the matching stephenlclarke/container and stephenlclarke/containerization commits.

Docs, STATUS.md, help support metadata, unit tests, runtime smoke tests, Docker Compose parity coverage, and Apple handoff templates are current for this slice.

References: Docker Compose top docs; apple/container#1769; stephenlclarke/container@d03f81b; stephenlclarke/containerization@d8b9585.

Release-Highlight: container compose top now supports Docker Compose-style process tables for selected services, including UID, PID, PPID, C, STIME, TTY, TIME, and CMD columns.

Release-Highlight: Live Docker Compose parity tests now use a matched source-built vminit image so runtime-backed compose features exercise the same guest service as the host API.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Request]: The container info command

3 participants