# How to Gracefully Stop a Container with a Custom Timeout in Apple Container

> Learn to gracefully stop an Apple Container with a custom timeout using the container stop command or Swift API. Ensure clean shutdowns before force termination.

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

---

**Use the `container stop` command with the `--timeout` flag to specify a custom grace period in seconds, or construct a `ContainerStopOptions` struct with `timeoutInSeconds` when calling the Swift API to ensure clean shutdowns before force termination.**

The apple/container repository provides a robust runtime for managing container lifecycles, including a configurable graceful shutdown mechanism. When you need to stop a running container, the runtime allows you to specify exactly how long to wait for the process to exit cleanly before sending a force-kill signal. This capability ensures that applications have sufficient time to flush state, complete in-flight requests, and release resources properly.

## Understanding the Graceful Stop Mechanism

The graceful stop implementation in [`RuntimeService.swift`](https://github.com/apple/container/blob/main/RuntimeService.swift) (lines 508-540) follows a predictable state machine that balances courtesy with enforcement. When you initiate a stop request, the runtime transitions the container to the **stopping** state, delivers your chosen signal (defaulting to `SIGTERM`), and waits for the specified duration.

If the container exits within the timeout window, the state transitions immediately to **stopped**. However, if the process ignores the signal and continues running past the deadline, the runtime automatically escalates to `SIGKILL` to guarantee termination. This two-phase approach prevents hung containers while respecting application cleanup needs.

## Using the CLI to Stop Containers with Custom Timeouts

The command-line interface provides straightforward flags for controlling shutdown behavior. The `--timeout` parameter accepts an integer representing seconds, while `--signal` optionally specifies which POSIX signal to send initially.

Stop a single container with a 15-second grace period:

```bash
container stop my-app --timeout 15

```

Specify both a custom signal and extended timeout:

```bash
container stop my-app --timeout 30 --signal SIGTERM

```

Key defaults to remember:
- **Default timeout**: 10 seconds if `--timeout` is omitted
- **Default signal**: `SIGTERM` if `--signal` is omitted
- **Per-container application**: When using `--all`, the timeout applies independently to each container

The CLI implementation in [`StopCommand.swift`](https://github.com/apple/container/blob/main/StopCommand.swift) parses these flags, constructs a `ContainerStopOptions` value, and forwards the request via [`RuntimeClient.swift`](https://github.com/apple/container/blob/main/RuntimeClient.swift) (lines 181-192) to the runtime service over XPC.

## Implementing Graceful Stops in Swift

For programmatic control, import the `ContainerResource` and `RuntimeClient` modules to build stop requests directly.

### Constructing ContainerStopOptions

The `ContainerStopOptions` struct, defined in [`ContainerStopOptions.swift`](https://github.com/apple/container/blob/main/ContainerStopOptions.swift), encapsulates the optional signal name and timeout duration:

```swift
import ContainerResource

let stopOpts = ContainerStopOptions(
    signal: "SIGTERM", 
    timeoutInSeconds: 15
)

```

Both parameters are optional; omitting them defaults to the runtime's standard values.

### Calling RuntimeClient.stop

Use the `RuntimeClient` to send the stop request asynchronously:

```swift
import RuntimeClient

let client = RuntimeClient(containerID: "my-app")
let opts = ContainerStopOptions(signal: "SIGTERM", timeoutInSeconds: 20)

Task {
    do {
        try await client.stop(options: opts)
        print("Container stopped gracefully")
    } catch {
        print("Failed to stop container: \(error)")
    }
}

```

The `stop` method packages the options into an XPC message and awaits the runtime's response, throwing errors if the container does not exist or if a stop-then-remove race condition occurs (as tested in [`TestCLIRmRace.swift`](https://github.com/apple/container/blob/main/TestCLIRmRace.swift)).

## How the Runtime Handles Stop Requests

According to the implementation in [`RuntimeService.swift`](https://github.com/apple/container/blob/main/RuntimeService.swift), the stop logic executes atomically to prevent state corruption:

1. **State transition**: Sets internal state to `.stopping`
2. **Signal delivery**: Sends the requested signal (e.g., `SIGTERM`) to the container's PID
3. **Timer activation**: Waits for the specified `timeoutInSeconds` duration
4. **Conditional escalation**: If state remains `.stopping` after timeout, sends `SIGKILL`
5. **Finalization**: Transitions state to `.stopped` and returns success

This implementation ensures that the operation is **idempotent**—stopping an already-stopped container returns success without error, simplifying error handling in orchestration scripts.

## Summary

- **Custom timeouts** prevent data loss by giving containers time to shut down cleanly while preventing indefinite hangs
- **CLI usage**: `container stop <id> --timeout <seconds>` with optional `--signal` flag
- **Swift API**: Construct `ContainerStopOptions` and call `RuntimeClient.stop(options:)`
- **Core implementation**: Located in [`RuntimeService.swift`](https://github.com/apple/container/blob/main/RuntimeService.swift) (lines 508-540) with state machine management
- **Safety guarantees**: Automatic `SIGKILL` escalation after timeout and idempotent operation design

## Frequently Asked Questions

### What is the default timeout when stopping a container?

The default timeout is **10 seconds** if you do not specify the `--timeout` flag in the CLI or omit the `timeoutInSeconds` parameter in the `ContainerStopOptions` struct. This provides a reasonable balance for most applications while preventing excessive wait times.

### Can I use a custom signal instead of SIGTERM?

Yes. Both the CLI `--signal` flag and the `ContainerStopOptions` initializer accept custom signal names such as `SIGINT`, `SIGUSR1`, or `SIGTERM`. The runtime parses the signal string and delivers it to the container's main process before beginning the timeout countdown.

### What happens if the container ignores the stop signal?

If the container process does not exit within the specified timeout period, the runtime automatically sends `SIGKILL` to force immediate termination. This escalation happens transparently in [`RuntimeService.swift`](https://github.com/apple/container/blob/main/RuntimeService.swift), ensuring the container reaches the `.stopped` state even when the application is unresponsive.

### Is the stop operation idempotent?

Yes. The stop operation is fully idempotent according to the test suite in [`TestCLIStop.swift`](https://github.com/apple/container/blob/main/TestCLIStop.swift). Calling `stop` on an already-stopped container returns success without modifying state or returning an error, making it safe to use in retry loops and orchestration workflows.