How the `--ssh` Flag Forwards SSH Authentication Sockets to Containers in Apple's Container Framework

The --ssh flag dynamically mounts the host's SSH agent socket into the container at /run/host-services/ssh-auth.sock and injects the SSH_AUTH_SOCK environment variable, enabling password-less SSH operations inside the container without static volume mounts.

The apple/container framework provides a seamless mechanism to forward your host's SSH authentication agent into running containers. Understanding how the --ssh option forward SSH authentication sockets to containers reveals a sophisticated implementation that re-evaluates the socket path on every container start, ensuring compatibility across different SSH agent configurations and session changes.

How the --ssh Flag Works Under the Hood

The implementation spans three coordinated components across the Container API service and the Linux runtime service.

Flag Parsing and Configuration

In Sources/Services/ContainerAPIService/Client/Flags.swift (lines 31-33), the CLI registers --ssh as a Boolean flag within the Management options struct. When the user includes --ssh in their command, the flag is stored as ssh = true in the container configuration.

public struct Management: ParsableArguments {
    @Flag(name: .long, help: "Forward SSH agent socket to container")
    public var ssh = false
}

This configuration is then passed to the runtime service when the container start request is initiated.

Socket Path Propagation

When the runtime receives a request with config.ssh enabled, it reads the host's SSH_AUTH_SOCK environment variable from the dynamic environment supplied via the XPC message (dynamicEnv). In Sources/Services/RuntimeLinux/Server/RuntimeService.swift (lines 80-95), the runtime creates a UnixSocketConfiguration that mounts the host socket at /var/host-services/ssh-auth.sock inside the VM.

if let socketUrl = Self.sshAuthSocketHostUrl(config: config,
                                            dynamicEnv: dynamicEnv,
                                            log: log) {
    let socketConfig = UnixSocketConfiguration(
        source: socketUrl,
        destination: URL(fileURLWithPath: Self.sshAuthSocketGuestPath),
        permissions: permissions,
        direction: .into)
    czConfig.sockets.append(socketConfig)
}

Environment Variable Injection

After mounting the socket, the runtime injects the SSH_AUTH_SOCK environment variable pointing to /run/host-services/ssh-auth.sock for the container's initial process. As implemented in RuntimeService.swift (lines 78-80), this occurs only if the variable is not already present in the container configuration:

if config.ssh {
    if !czConfig.process.environmentVariables
          .contains(where: { $0.starts(with: "\(Self.sshAuthSocketEnvVar)=") }) {
        czConfig.process.environmentVariables.append(
            "\(Self.sshAuthSocketEnvVar)=\(Self.sshAuthSocketGuestPath)")
    }
}

Inside the container, /run/host-services/ssh-auth.sock is a symlink that points to the guest-side mount at /var/host-services/ssh-auth.sock.

Dynamic Updates on Container Restart

Unlike static volume mounts, the --ssh flag re-evaluates the host's current SSH_AUTH_SOCK value every time the container starts. This design, documented in docs/how-to.md, means that logging out, changing the socket location, or switching SSH agents is handled transparently without requiring container rebuilds or manual flag updates.

Practical Usage Example

To forward your SSH authentication socket when running a container:


# On the host (macOS)

$ export SSH_AUTH_SOCK=/private/tmp/com.apple.launchd.XYZ/Listeners
$ container run -it --rm --ssh alpine:latest sh
/ # env | grep SSH_AUTH_SOCK

SSH_AUTH_SOCK=/run/host-services/ssh-auth.sock
/ # ssh-add -l          # shows the keys from the host agent

/ # git clone [email protected]:myorg/private-repo.git

This grants the container immediate access to your host's SSH keys without copying them into the image or exposing private key material.

Summary

  • The --ssh flag is defined in Sources/Services/ContainerAPIService/Client/Flags.swift as a Boolean option that triggers socket forwarding when set to true.
  • The runtime service reads SSH_AUTH_SOCK from the host's dynamicEnv and creates a UnixSocketConfiguration to mount it at /var/host-services/ssh-auth.sock inside the VM.
  • The environment variable SSH_AUTH_SOCK is automatically set to /run/host-services/ssh-auth.sock for the container process if not already specified.
  • The socket path is re-evaluated on every container start, supporting dynamic SSH agent changes and transparent session updates.

Frequently Asked Questions

What is the exact path where the SSH socket is mounted inside the container?

The host socket is mounted at /var/host-services/ssh-auth.sock inside the VM, while the container process sees it at /run/host-services/ssh-auth.sock via a symlink. The SSH_AUTH_SOCK environment variable is automatically set to point to this location for the container's initial process.

Does the --ssh flag work if I change my SSH agent after starting the container?

The --ssh flag re-evaluates the host's SSH_AUTH_SOCK path every time the container starts, not just during the initial creation. This means you can change your SSH agent or socket location between restarts, and the container will automatically use the current configuration without requiring manual updates or container rebuilds.

How is the --ssh flag defined in the Container API service?

In Sources/Services/ContainerAPIService/Client/Flags.swift (lines 31-33), the flag is declared as a Swift @Flag within the Management struct: @Flag(name: .long, help: "Forward SSH agent socket to container") public var ssh = false. This makes it available as --ssh in the CLI and stores the boolean value in the container configuration passed to the runtime.

Can I manually set SSH_AUTH_SOCK instead of using the --ssh flag?

While you can manually set environment variables, the --ssh flag provides additional functionality by creating the underlying Unix socket mount configuration through UnixSocketConfiguration. Manual settings would not create the necessary socket forwarding between the host and guest VM without equivalent runtime configuration in RuntimeService.swift.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →