How to Execute a Command Inside a Running Container Using Apple Container

Use container exec <container-name> <command> to run arbitrary processes inside live containers, with support for interactive TTY, detached mode, and custom users/environments.

The apple/container repository provides a lightweight container runtime for macOS that includes a Docker-compatible exec command. When you need to execute a command inside a running container, Apple Container bridges the CLI to the underlying XPC-based sandbox runtime while preserving the original container's isolation guarantees.

How container exec Works Under the Hood

The execution logic resides in Sources/ContainerCommands/Container/ContainerExec.swift, specifically within the Application.ContainerExec struct and its run() method. When you invoke container exec, the CLI performs a seven-step sequence to spawn a sandboxed process in the target container's namespace.

Container Lookup and State Validation

First, a ContainerClient instance contacts the local container daemon via client.get(id:) to fetch container metadata. The helper ensureRunning(container:) validates that the container is live before proceeding, raising an error if the target is not running.

Process Configuration and I/O Setup

The CLI builds a process configuration starting from the container's initProcess configuration (container.configuration.initProcess). It overrides the executable with your command-line argument, sets arguments, and applies optional flags parsed by Parser.user and Parser.allEnv for user/group handling and environment variables.

Then, ProcessIO.create constructs the appropriate stdin/stdout/stderr pipes, allocating a pseudo-terminal when --tty is specified or attaching the host's stdin when --interactive is set.

Execution Modes and Signal Handling

Finally, client.createProcess sends the configuration to the container runtime to obtain a Process handle. If --detach (-d) is specified, the process starts in the background and the CLI prints the container ID immediately. Otherwise, the CLI forwards signals like SIGINT and SIGTERM to the child process, monitors execution, and returns the exit code to your shell.

Practical Usage Examples

Here are common patterns for executing commands inside running containers:


# Run a one-off command (list files) inside a running container named "my-web-server"

container exec my-web-server ls /var/www

# Open an interactive shell (TTY + stdin) inside the container

container exec -it my-web-server /bin/bash

# Run a background command inside the container (detached)

container exec -d my-web-server /usr/bin/python3 /opt/app/background_worker.py

# Override the user and group for the executed process

container exec --user nobody --gid 1000 my-web-server cat /etc/passwd

# Set environment variables and a custom working directory

container exec -e DEBUG=1 -w /app my-web-server ./run-tests.sh

Key Implementation Files

Understanding these core files helps when debugging or extending exec functionality:

Summary

  • container exec is the primary CLI command for running processes inside live Apple Containers.
  • The implementation in ContainerExec.swift validates container state, builds process configurations, and handles I/O through ProcessIO.create.
  • You can run commands in attached mode (default) with signal forwarding and exit code propagation, or detached mode (-d) for background tasks.
  • Interactive features like TTY (-t) and stdin attachment (-i) reuse the same sandboxing guarantees as the original container process.
  • User permissions, environment variables, and working directories can be overridden per-command using flags parsed by Parser.swift.

Frequently Asked Questions

How do I run an interactive shell inside a running Apple Container?

Use the -i (interactive) and -t (TTY) flags together: container exec -it my-web-server /bin/bash. This allocates a pseudo-terminal and attaches your stdin, allowing you to interact with the shell as if you were running locally.

What is the difference between detached and attached exec mode?

Attached mode (default) forwards signals from your terminal to the container process and waits for completion, returning the exit code. Detached mode (-d) starts the process in the background via client.createProcess, prints the container ID immediately, and exits without waiting for the command to finish.

How does Apple Container handle user and group permissions for exec commands?

The Parser.swift module parses --user and --gid flags to override the process owner. If unspecified, the command runs with the same user context as the container's original init process, preserving the sandbox's security boundaries.

Can I set environment variables when executing a command in a running container?

Yes. Use the -e flag to set individual variables (e.g., -e DEBUG=1) or --env-file to load from a file. The Parser.allEnv logic in ContainerExec.swift merges these with the container's existing environment before spawning the process.

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 →