# How to Use `container exec` to Run Commands in Running Containers

> Learn how to use container exec to run commands in running containers. This guide explains the process, configuration, and RPC invocation for creating new sandboxed processes.

- Repository: [Apple/container](https://github.com/apple/container)
- Tags: how-to-guide
- Published: 2026-07-07

---

**The `container exec` command creates a new sandboxed process inside an already-running container by validating the container state, building a `ProcessConfiguration`, and invoking the daemon's `createProcess` RPC.**

The `container exec` subcommand in the Apple Container project lets you execute additional commands inside containers that are already running. According to the source code in the `apple/container` repository, this functionality is implemented in [`Sources/ContainerCommands/Container/ContainerExec.swift`](https://github.com/apple/container/blob/main/Sources/ContainerCommands/Container/ContainerExec.swift) as the `Application.ContainerExec` type. It provides functionality similar to Docker's `docker exec`, but built on Apple's sandbox-based container runtime.

## How `container exec` Works Internally

The implementation follows a seven-step flow that bridges the CLI interface to the sandboxed process execution.

### Step 1: Resolve the Target Container

The command first creates a `ContainerClient` to contact the local daemon. It fetches the container metadata using `client.get(id: containerId)` and validates that the container is actually running via `ensureRunning(container:)`. If the container is stopped or doesn't exist, the command fails immediately.

### Step 2: Build the Process Configuration

The first positional argument becomes the executable, while remaining arguments are passed as its arguments. The code starts with the container's base `initProcess` configuration from `container.configuration.initProcess`, then overrides specific fields: `config.executable = executable` and `config.arguments = ...`. Flags like `--tty`, `--env`, `--workdir`, and `--user` are parsed in [`ProcessUtils.swift`](https://github.com/apple/container/blob/main/ProcessUtils.swift) and merged into a `ProcessConfiguration` struct.

### Step 3: Prepare I/O Streams

The `ProcessIO.create(tty:interactive:detach:)` method constructs the appropriate stdin, stdout, and stderr pipes. When `--tty` is specified, it allocates a pseudo-terminal; otherwise, it uses standard pipes. This handles the redirection between the host and the sandboxed process.

### Step 4: Create the Remote Process

With the configuration assembled, the client calls `client.createProcess(containerId: container.id, configuration: config, stdio: io.stdio)`. This RPC request asks the daemon to spawn a new sandboxed process inside the target container.

### Step 5: Handle Detached Execution

If the `--detach` flag is supplied, the CLI starts the process, immediately closes its I/O handles, prints the container ID, and returns. The process continues running in the background within the container.

### Step 6: Manage Foreground Execution

For non-detached runs, the code installs a `SignalThreshold` handler to protect against endless `SIGINT` or `SIGTERM` loops (terminating after three signals). The `io.handleProcess(process:log:)` method then drives the I/O streams until the process exits, capturing the exit code.

### Step 7: Propagate Exit Status

Finally, the command throws `ArgumentParser.ExitCode(exitCode)` to ensure the `container exec` CLI exits with the same status code as the process running inside the container.

## Common Usage Examples

The following examples demonstrate the most common patterns for running commands inside active containers, as documented in the project's [`docs/tutorials/start-here.md`](https://github.com/apple/container/blob/main/docs/tutorials/start-here.md).

### Run a Simple One-Off Command

Execute a single command and return the output:

```bash
container exec my-web-server ls /content

```

This runs `ls /content` inside the container named `my-web-server` and streams the results to your terminal.

### Start an Interactive Shell

Allocate a pseudo-terminal and connect your host keyboard to a shell inside the container:

```bash
container exec --tty --interactive my-web-server sh

```

The `--tty` flag tells the shell it has a terminal attached, while `--interactive` streams your keystrokes to the process.

### Run a Detached Background Process

Start a process that outlives your CLI session:

```bash
container exec --detach my-web-server sleep 3600

```

This begins a `sleep` process that runs for one hour, detaches the client immediately, and prints the container ID.

### Set Custom Environment and Working Directory

Inject environment variables and change the working directory before execution:

```bash
container exec \
  --env FOO=bar \
  --workdir /app \
  my-web-server ./run-my-script.sh

```

The `FOO=bar` environment variable is added to the process environment, and the command executes from `/app` instead of the default working directory.

### Execute as a Non-Root User

Run commands with specific user privileges:

```bash
container exec \
  --user alice \
  my-web-server id -u -n

```

This executes `id -u -n` as user `alice` inside the container, provided that user exists in the container's user namespace.

## Summary

- **`container exec`** creates new processes inside already-running containers via the `Application.ContainerExec` type in [`Sources/ContainerCommands/Container/ContainerExec.swift`](https://github.com/apple/container/blob/main/Sources/ContainerCommands/Container/ContainerExec.swift).
- The command validates the container is running via `ensureRunning(container:)`, builds a `ProcessConfiguration`, and uses `ProcessIO` to handle input/output streams.
- **Detached mode** (`--detach`) runs processes in the background, while foreground mode attaches your terminal and handles signals via `SignalThreshold`.
- Key flags include `--tty` for pseudo-terminals, `--interactive` for stdin attachment, `--env` for environment variables, `--workdir` for directory changes, and `--user` for privilege switching.
- All functionality relies on the `ContainerClient` RPC interface defined in [`Sources/ContainerAPIClient/ContainerClient.swift`](https://github.com/apple/container/blob/main/Sources/ContainerAPIClient/ContainerClient.swift).

## Frequently Asked Questions

### What is the difference between `container exec` and `container run`?

`container run` creates and starts a new container from an image, while `container exec` starts a new process inside an existing running container. The exec command requires the target container to already be in a running state, which it verifies via `ensureRunning(container:)` before attempting to create the process.

### How does `container exec` handle terminal signals like Ctrl+C?

The implementation installs a `SignalThreshold` handler that counts `SIGINT` and `SIGTERM` signals. After receiving three termination signals, the CLI exits forcefully to prevent infinite loops. Before that threshold, signals are propagated appropriately to the container process through the `ProcessIO` stream handling.

### Can I execute commands as root if the container normally runs as a non-root user?

Yes. The `--user` flag allows you to specify any valid user in the container's user namespace, including root (uid 0), provided the container configuration permits it. The user is parsed in [`Sources/ContainerCommands/Container/ProcessUtils.swift`](https://github.com/apple/container/blob/main/Sources/ContainerCommands/Container/ProcessUtils.swift) and passed to the daemon as part of the `ProcessConfiguration`.

### Why does `container exec` fail with "container is not running"?

The command explicitly checks the container state using `client.get(id:containerId)` followed by `ensureRunning(container:)`. If the container is stopped, paused, or hasn't finished initializing, the RPC will fail. You must start the container with `container run` or `container start` before using `container exec`.