How to Execute Commands in a Running Container with Apple Container

Apple Container provides the container exec sub-command to run arbitrary processes inside live containers by creating a new sandboxed process in the target container's namespace.

The apple/container repository implements a modern container runtime for macOS that allows you to execute commands in already-running containers without interrupting the main process. This capability is essential for debugging, administration, and maintenance tasks that require access to a container's internal environment.

The container exec Command Structure

The container exec command re-uses the same process-configuration plumbing as container run, but targets an existing container rather than creating a new one. When you invoke this command, the CLI performs a sequence of operations defined in Sources/ContainerCommands/Container/ContainerExec.swift【 L23-L45 】.

The basic syntax follows this pattern:

container exec [OPTIONS] CONTAINER COMMAND [ARG...]

The command accepts several flags to control execution behavior:

  • -i, --interactive – Keep stdin open
  • -t, --tty – Allocate a pseudo-TTY
  • -d, --detach – Run command in background
  • -u, --user – Specify username or UID
  • -g, --gid – Specify group ID
  • -e, --env – Set environment variables
  • -w, --workdir – Set working directory

Internal Execution Flow

The implementation in ContainerExec.swift handles the entire lifecycle of executing commands in running containers through the following stages:

Container Lookup and Validation

First, the CLI establishes a ContainerClient connection to the local container daemon and fetches container metadata using client.get(id:)【 L46-L114 】. The ensureRunning(container:) function validates that the target container is active; if the container is stopped or non-existent, the command raises an error immediately.

Process Configuration Building

The execution logic starts from the container's existing init process configuration (container.configuration.initProcess) and overrides specific fields:

  • Executable: Replaced with the command supplied on the CLI
  • Arguments: Populated with remaining command-line arguments
  • User/Group: Modified via Parser.user and Parser.allEnv from Sources/ContainerPlugin/Parser.swift when flags are provided
  • Environment: Merged with variables from -e flags and optional env files

I/O Setup and Process Creation

The ProcessIO.create method (defined in Sources/ContainerBuild/ProcessIO.swift) constructs the appropriate stdin/stdout/stderr pipes. When --tty is specified, it allocates a pseudo-terminal; when --interactive is set, it attaches the host's stdin stream.

Finally, client.createProcess transmits the configuration to the container runtime and returns a Process handle. If --detach is specified, the CLI prints the process ID and exits immediately. Otherwise, it forwards signals (such as SIGINT and SIGTERM) to the child process, monitors execution, and returns the exit code to the shell.

Practical Usage Examples

The docs/command-reference.md file【 L52-L71 】documents several common patterns for executing commands in running containers.

Run One-Off Commands

Execute a single command and return the output:

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

Interactive Shell Access

Open a bash shell with TTY allocation and interactive stdin:

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

Background Execution

Run long-running tasks without blocking the terminal:

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

Override Security Context

Execute commands as a different user or group:

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

Custom Environment and Working Directory

Set environment variables and change the working directory:

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

Summary

  • container exec is the primary interface for running commands in existing Apple Containers
  • The command validates container state via ensureRunning(container:) before attempting execution
  • Process configuration inherits from the container's init process but allows overrides for executable, arguments, user, group, and environment
  • I/O handling supports three modes: attached (default), interactive (-it), and detached (-d)
  • All execution logic resides in Sources/ContainerCommands/Container/ContainerExec.swift, with supporting utilities in ContainerClient.swift and ProcessIO.swift

Frequently Asked Questions

How do I run an interactive shell in a container?

Use the -i and -t flags together to allocate a pseudo-terminal and keep stdin open. For example: container exec -it my-container /bin/bash. This provides a fully interactive shell session inside the running container's namespace while preserving all isolation guarantees.

Can I run background processes with container exec?

Yes, add the -d or --detach flag to run the process in the background. The CLI will print the process ID and exit immediately without waiting for completion. This is useful for starting background workers or services without blocking your terminal session.

How do I specify a different user when executing commands?

Use the --user flag followed by a username or UID, and optionally --gid for the group ID. For instance: container exec --user 1000 --gid 1000 my-container whoami. This overrides the default user defined in the container configuration for that specific process.

What happens if the container is not running?

The command will fail with an error before attempting to create the process. The ensureRunning(container:) validation check in ContainerExec.swift verifies the container state during the initial lookup phase, ensuring you cannot execute commands in stopped or non-existent containers.

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 →