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:
Sources/ContainerCommands/Container/ContainerExec.swift– Implements thecontainer execcommand logic, including argument parsing and therun()method that orchestrates process creation.Sources/ContainerAPIClient/ContainerClient.swift– Provides the client interface to the container daemon, used byexecto fetch containers and create processes.Sources/ContainerBuild/ProcessIO.swift– Helper for constructing stdio streams, handling TTY allocation and interactive mode.Sources/ContainerPlugin/Parser.swift– Parses environment-file (--env-file) and user/group flags for process configuration.docs/command-reference.md– Official documentation forcontainer execsyntax and options.
Summary
container execis the primary CLI command for running processes inside live Apple Containers.- The implementation in
ContainerExec.swiftvalidates container state, builds process configurations, and handles I/O throughProcessIO.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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →