Add Apple container runtime support#205
Conversation
Adds Apple's 'container' CLI (https://github.com/apple/container) as a container runtime alongside docker and podman, so 'aspire run' can orchestrate containers on macOS using the native Apple runtime. - New internal/applecontainer orchestrator driving the Apple container CLI: container/volume/network/image lifecycle, exec, log streaming (with synthesized timestamps), polling-based container/network events (the CLI has no 'events' command), per-object inspection (Apple inspect commands are all-or-nothing), and Apple-specific JSON parsing. - DCP-managed networks map onto the built-in 'default' network via the SingleNetworkOrchestrator capability: the runtime cannot create attachable custom networks (containers can only reach their own network's gateway, which would break container-to-host connectivity). - Network aliases are resolved by a small DNS forwarder (cmd/dcpdns) that DCP runs in a container on the default network: every container is pointed at it via --dns, DCP publishes container-name and alias records to it through a bind-mounted hosts file, and unknown names are relayed to the network gateway. The runtime itself has no alias support and serves no DNS records for container names. - Files and PEM certificates are baked into a content-addressed derived image at creation time (CreateFilesRequiresRunningContainer), since the runtime can only copy files into a running container. Derived images are cached by base image ID + layer digests, so unchanged inputs skip the rebuild across runs. - Label values containing '=' are transparently base64-encoded (the Apple CLI rejects them) and decoded when read back. - 'dcp info --container-runtime container' reports installed/running status, gateway-based hostName, and actionable errors when the system services are stopped. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
7999c92 to
c6d992e
Compare
|
@slang25 thanks for looking into Apple container support. I don’t think we’re ready to enable it in DCP yet because there are still several runtime gaps that affect core Aspire/DCP scenarios. The main blockers are around feature parity with Docker/Podman: container-to-container DNS/network aliases, health checks, restart policy support, attach/terminal behavior, reliable detached exit-code reporting, and some file-copy/image customization flows. Some of these can be partially worked around in DCP, but not all of them are clean or complete enough yet. We did already make progress in DCP with #198, which creates containers with their initial network attachments instead of creating first and attaching later. That helps with runtimes where networking has to be decided at creation time, including Apple container. There are also upstream issues/PRs that would close some of the remaining gaps, such as Apple container DNS/network alias support (apple/container#1809, apple/container#1839). I’m also planning to try opening a PR against Apple container’s cp command to add tar stream/file archive support. That would let DCP handle some file injection/customization paths without depending on tar existing inside the target image. So overall: Apple container support is still something we want to keep moving toward, but I’d prefer not to claim support until the remaining runtime gaps are either fixed upstream or we have robust DCP-side fallbacks. |
|
@danegsta thanks, that makes sense. I'll hold off here. Funnily enough I did some similar work to add the tar functionality for socktainer, which has the same issues we are trying to solve here, which is "how do we bridge the gaps between how Apple containers work vs Docker". What I've added in this PR mirrors socktainer, which is to add a dns forwarder, however a baked in solution would be nicer for sure. |
|
I'm hoping they'll be open to a PR that uses native interop to perform the required tar operations. I've been planning on putting together a tracking issue next week for all the features we need that are missing so we have a relatively definitive list. Obviously some like missing events support are more annoyances than full blockers. |
Add Apple
containerruntime supportSummary
Adds Apple's
containerCLI as a container runtime alongsidedockerandpodman, soaspire runcan orchestrate containers on macOS (Apple Silicon) using the native Apple runtime — no Docker Desktop required.Motivation
Aspire already supports
containeras a publishing runtime; DCP was the blocker for the run path (--container-runtime containerwas rejected). The Apple runtime differs structurally from Docker/Podman — each container runs in its own lightweight VM with a routable IP — and several Docker-isms simply don't exist. This PR adapts DCP's orchestration model to those constraints. All CLI behavior was validated againstcontainerCLI 1.0.0 on a real machine.What's included
New
containerorchestrator (internal/applecontainer)ContainerOrchestratorimplementation: container/volume/network/image lifecycle, exec, log streaming, and Apple-specific JSON parsing (the schemas are unrelated to Docker's).eventscommand, so container/network events are synthesized by diffing runtime snapshots (2s/5s), keeping DCP's event-driven reconciliation working.inspectcommands are all-or-nothing; multi-object inspections are issued one at a time to preserve DCP's partial-success contracts.logshas no--timestamps), client-side label filtering (lshas no--filter), host-port allocation for dynamic ports (-prequires an explicit host port), and created-vs-exited disambiguation (the runtime reports both asstopped; distinguished viastartedDate).--labelvalues containing=(which DCP's mount-tracking labels use); such values are transparently base64-encoded and decoded when read back.dcp inforeports gateway-basedhostName, versions in diagnostic mode, and an actionable error (container system start) when the system services are stopped.Single-network mapping (
SingleNetworkOrchestratorcapability)The runtime cannot connect/disconnect containers after creation, and containers can only reach their own network's gateway (verified: another network's gateway is unreachable), so per-app custom networks would break container→host connectivity via the single
hostNameDCP reports. Instead, DCP-managedContainerNetworks map onto the built-indefaultnetwork:ContainerNetworkstatus instead of creating anything; deletion is a no-op.ConnectNetworkverifies attachment (reportingErrAlreadyExists, which callers treat as success), and the attached-container list for network inspection is synthesized from the container list.DNS forwarder for network aliases (
cmd/dcpdns,internal/dcpdns)The runtime has no
--network-aliasequivalent and its gateway DNS serves no records for container names (verified empirically;container system dnsrequires sudo and only configures host-side resolution). Without aliases, container-to-container references and tunnel-proxy DNS names cannot resolve. DCP therefore provides name resolution itself:dcpdns) answers A-record queries for names DCP publishes and relays everything else verbatim to the network gateway, so regular resolution is unaffected. Ships asdcpdns_c(static Linux binary) alongsidedcp, mirroringdcptun_c.FROM scratchimage from it (once per DCP version, same pattern as the tunnel-proxy client image) and runs one forwarder container per DCP instance, labeled with the standard creator-process labels so the resource harvester removes it when the DCP process exits.--dns. Records (container name + network aliases → container IP) are published by atomically rewriting a hosts-format file in a host directory bind-mounted read-only into the forwarder; the forwarder re-reads it with bounded (~1s) staleness. Records are retracted on stop/remove.--dnsand keep gateway-only resolution — alias lookups fail but nothing else regresses.Verified live end-to-end: a container created before an aliased container resolves the alias to the right IP, container names resolve, external names resolve through the relay, and records are retracted on container removal.
Baked file injection + content-addressed derived-image cache
The runtime can only copy files into a running container, so the created-then-copy flow (dev certs,
WithContainerFiles) cannot work. Instead:CreateFilesRequiresRunningContainer()on theCreateFilesinterface (false for Docker/Podman). When true, the container controller bakes configured files and PEM certificates into a derived image as tar layers before creation, so they are present (with exact ownership/permissions) before the entrypoint runs.repo:tag-dcp-<sha256(baseImageID + layerDigests)>with deterministic layer mod-times — and reused on cache hits, so unchanged inputs never rebuild across runs. This also benefits Docker/Podman image-layer users (previously the derived tag was lifecycle-key based).BuildCreateFilesTarhelper produces the tar archives (usable as copy payloads or image layers).Known limitations (inherent to Apple container 1.0)
container start --attachdoes propagate exit codes; a follow-up could exploit that with an attached observer process.)attach— omitted from generated commands (DCP-level health probes are unaffected); the Terminal feature returns a clear unsupported error.CreateFileson a running container requirestarin the image (only relevant for direct API use; the controller path uses baked layers).Testing
BuildCreateFilesTar, the derived-image cache build/skip matrix, DNS wire-format parsing/answering, records-file reloads, an in-process forwarder end-to-end test (authoritative + relay paths against a fake upstream), and DNS record publishing/retraction.DCP_TEST_APPLE_CONTAINER=1, macOS + Apple container required): full lifecycle against the real runtime — pull, run with ports/volumes/labels, list/filter, inspect, exec, file injection, log capture, derived-image bake via the real builder, network verification, DNS alias resolution between containers via the real forwarder, stop/remove, volume cleanup.go build ./...,make lint(0 issues), full unit + integration test suite passes.dcp info --container-runtime containervalidated in healthy, stopped-services, and diagnostics modes; runtime auto-detection prefers a running Apple runtime only when Docker/Podman are unavailable (docker remains the tie-break default).Why draft
aspire runvalidation (acceptance criteria: endpoint reachable, service discovery, cleanup, dashboard logs) still needs an Aspire build that passes--container-runtime containerthrough to DCP.