# OpenSandbox TTL Expiration: Automatic Sandbox Termination Explained

> Learn how OpenSandbox automatically terminates containers when TTL expires. Understand the sandbox state transition to Terminated with reason ttl_expiry.

- Repository: [Alibaba/OpenSandbox](https://github.com/alibaba/OpenSandbox)
- Tags: internals
- Published: 2026-03-08

---

**When an OpenSandbox sandbox exceeds its Time To Live (TTL) expiration, the platform automatically force-terminates the container or pod, transitions the state from `Running` or `Paused` to `Terminated`, and sets the termination reason to `ttl_expiry`.**

OpenSandbox enforces strict resource lifecycle management through configurable Time To Live (TTL) settings that prevent resource leaks in multi-tenant environments. When a sandbox's TTL expiration deadline passes, the system triggers an irreversible automatic cleanup process that ensures resources are reclaimed and the sandbox enters a terminal state. Understanding this behavior is critical for developers building applications that rely on ephemeral execution environments.

## What Happens When OpenSandbox TTL Expiration Occurs

When the TTL expiration timer fires, OpenSandbox initiates a forced termination sequence that executes immediately regardless of whether the sandbox is currently processing workloads or in a `Paused` state.

### The Termination State Transition

The lifecycle follows a strict progression defined in the OpenSandbox specification:

1. The sandbox transitions from its current active state to `Stopping`
2. The system executes cleanup operations on the container or pod
3. The final state becomes `Terminated` with `status.reason` explicitly set to `"ttl_expiry"`

This transition is atomic and irreversible once the expiration timer triggers. The [`specs/sandbox-lifecycle.yml`](https://github.com/alibaba/OpenSandbox/blob/main/specs/sandbox-lifecycle.yml) file formally defines TTL expiry as a valid termination trigger that mandates the `Stopping → Terminated` transition.

### Timer-Based Cleanup Process

A background timer created during sandbox initialization fires at the exact `expires_at` timestamp. This timer invokes backend-specific expiration handlers:

- **Docker backend**: Calls `_expire_sandbox` in [`server/src/services/docker.py`](https://github.com/alibaba/OpenSandbox/blob/main/server/src/services/docker.py)
- **Kubernetes backend**: Executes equivalent logic through the provider's `update_expiration` and `get_expiration` methods

The cleanup process performs the following actions in sequence:
- Kills the running container or pod if still active
- Removes the container/pod and associated network resources
- Cleans up side-car components such as egress proxies
- Deletes the expiration tracker to prevent post-termination renewal attempts

## Implementation Details: Docker and Kubernetes Backends

OpenSandbox supports multiple container backends, each implementing TTL expiration with backend-specific optimizations while maintaining consistent state semantics across the API.

### Docker Backend Implementation

In [`server/src/services/docker.py`](https://github.com/alibaba/OpenSandbox/blob/main/server/src/services/docker.py), the Docker service manages TTL through three key functions that ensure reliable expiration handling:

- `_schedule_expiration`: Registers a background `Timer` when a sandbox launches that fires at the `expires_at` timestamp (lines 256-270)
- `_expire_sandbox`: Executes the forced termination, killing and removing the container along with its side-car proxy (lines 95-124)
- `_restore_existing_sandboxes`: Rebuilds expiration timers when the service restarts, immediately terminating any sandboxes that expired while the service was offline (lines 23-62)

The `_expire_sandbox` function ensures that even if the container is unresponsive to graceful shutdown signals, it is forcefully removed and the sandbox record updated to `Terminated` with the `ttl_expiry` reason.

### Kubernetes Backend Handling

The Kubernetes implementation in [`server/src/services/k8s/kubernetes_service.py`](https://github.com/alibaba/OpenSandbox/blob/main/server/src/services/k8s/kubernetes_service.py) relies on the provider's native expiration mechanisms while maintaining API compatibility with the Docker backend. While the `renew_expiration` endpoint validates future timestamps through `ensure_future_expiration` in [`server/src/services/validators.py`](https://github.com/alibaba/OpenSandbox/blob/main/server/src/services/validators.py), the actual enforcement occurs through:

- Provider-specific `update_expiration` and `get_expiration` methods
- Automatic pod termination when the TTL deadline passes
- Transition to `Terminated` state with `ttl_expiry` reason, consistent with the Docker backend behavior

## Code Examples: Working with TTL Expiration

Developers can observe and interact with TTL expiration through the OpenSandbox API to build resilient applications that handle ephemeral execution environments gracefully.

### Creating a Sandbox with a 30-Second TTL

The following Python script creates a sandbox with a 30-second TTL and polls until automatic termination occurs:

```python
import requests
import datetime
import time

# Create sandbox with 30-second TTL

payload = {
    "image": {"uri": "python:3.11"},
    "timeout": 30,  # seconds

    "metadata": {"example": "ttl-demo"}
}

resp = requests.post(
    "http://localhost:8080/v1/sandboxes",
    json=payload,
    headers={"OPEN-SANDBOX-API-KEY": "my-key"}
)

sandbox = resp.json()
sid = sandbox["id"]
print(f"Sandbox {sid} created, expires at {sandbox['expiresAt']}")

# Poll until TTL expiration triggers termination

while True:
    info = requests.get(
        f"http://localhost:8080/v1/sandboxes/{sid}",
        headers={"OPEN-SANDBOX-API-KEY": "my-key"}
    ).json()
    
    state = info["status"]["state"]
    print(f"[{datetime.datetime.now()}] state={state}")
    
    if state == "Terminated":
        print("Sandbox terminated due to TTL expiration.")
        break
    
    time.sleep(5)

```

The timer created in `_schedule_expiration` fires after approximately 30 seconds, executing `_expire_sandbox` and moving the sandbox to the `Terminated` state.

### Checking Termination Status via API

After TTL expiration, query the sandbox status to confirm the termination reason:

```bash
curl -s -H "OPEN-SANDBOX-API-KEY: my-key" \
     http://localhost:8080/v1/sandboxes/<sandbox-id> | jq '.status'

```

Expected response:

```json
{
  "state": "Terminated",
  "reason": "ttl_expiry",
  "message": "Sandbox terminated because its TTL expired."
}

```

The `reason` field is populated by the lifecycle transition defined in [`specs/sandbox-lifecycle.yml`](https://github.com/alibaba/OpenSandbox/blob/main/specs/sandbox-lifecycle.yml).

### Renewing TTL Before Expiration

To prevent automatic termination, extend the TTL before the deadline passes:

```python
import datetime
import requests

# Extend TTL by 5 minutes

new_expiration = (datetime.datetime.utcnow() + datetime.timedelta(minutes=5)).isoformat() + "Z"

renew_resp = requests.post(
    f"http://localhost:8080/v1/sandboxes/{sid}/renew-expiration",
    json={"expiresAt": new_expiration},
    headers={"OPEN-SANDBOX-API-KEY": "my-key"}
)

print("New expiresAt:", renew_resp.json()["expiresAt"])

```

The `ensure_future_expiration` validator in [`server/src/services/validators.py`](https://github.com/alibaba/OpenSandbox/blob/main/server/src/services/validators.py) ensures the new timestamp is in the future before updating the timer.

## Key Source Files and Functions

Understanding the TTL expiration mechanism requires familiarity with these components:

| File | Function/Component | Purpose |
|------|-------------------|---------|
| [`specs/sandbox-lifecycle.yml`](https://github.com/alibaba/OpenSandbox/blob/main/specs/sandbox-lifecycle.yml) | Lifecycle specification | Defines `ttl_expiry` as valid termination reason and `Stopping → Terminated` transition |
| [`server/src/services/docker.py`](https://github.com/alibaba/OpenSandbox/blob/main/server/src/services/docker.py) | `_schedule_expiration` | Creates background timer at sandbox creation (lines 256-270) |
| [`server/src/services/docker.py`](https://github.com/alibaba/OpenSandbox/blob/main/server/src/services/docker.py) | `_expire_sandbox` | Executes forced termination and cleanup (lines 95-124) |
| [`server/src/services/docker.py`](https://github.com/alibaba/OpenSandbox/blob/main/server/src/services/docker.py) | `_restore_existing_sandboxes` | Rebuilds timers on service restart, expires overdue sandboxes (lines 23-62) |
| [`server/src/services/k8s/kubernetes_service.py`](https://github.com/alibaba/OpenSandbox/blob/main/server/src/services/k8s/kubernetes_service.py) | Provider expiration methods | Kubernetes-specific TTL enforcement |
| [`server/src/services/validators.py`](https://github.com/alibaba/OpenSandbox/blob/main/server/src/services/validators.py) | `ensure_future_expiration` | Validates renewal timestamps are in the future |
| [`server/src/api/lifecycle.py`](https://github.com/alibaba/OpenSandbox/blob/main/server/src/api/lifecycle.py) | `renew_expiration` endpoint | HTTP API for TTL extension |

## Summary

- **Automatic termination**: When OpenSandbox TTL expiration occurs, the platform immediately force-terminates the sandbox without manual intervention.
- **State transition**: The sandbox moves from `Running` or `Paused` to `Stopping`, then `Terminated` with `reason` set to `ttl_expiry`.
- **Timer mechanism**: A background timer created at launch triggers `_expire_sandbox` (Docker) or equivalent Kubernetes logic at the exact expiration timestamp.
- **Resource cleanup**: The process kills the container/pod, removes network resources, cleans up side-car components like egress proxies, and deletes the expiration tracker.
- **Service resilience**: On restart, `_restore_existing_sandboxes` rebuilds timers and immediately terminates any sandboxes that expired while the service was offline.

## Frequently Asked Questions

### Can I renew a sandbox's TTL after it has already expired?

No. Once OpenSandbox TTL expiration occurs and the sandbox enters the `Terminated` state, the expiration tracker is deleted and the sandbox cannot be renewed. The `ensure_future_expiration` validator in [`server/src/services/validators.py`](https://github.com/alibaba/OpenSandbox/blob/main/server/src/services/validators.py) only accepts future timestamps for active sandboxes, and the `_expire_sandbox` function removes all tracking data upon termination. You must create a new sandbox with a fresh TTL.

### What happens to side-car components like egress proxies when TTL expires?

The `_expire_sandbox` function in [`server/src/services/docker.py`](https://github.com/alibaba/OpenSandbox/blob/main/server/src/services/docker.py) (lines 95-124) explicitly cleans up side-car components alongside the main container. When the TTL timer fires, the function removes egress proxies and any associated network resources as part of the standard cleanup sequence. This ensures complete resource reclamation and prevents orphaned containers or networking rules from persisting after the sandbox terminates.

### Does OpenSandbox handle TTL expiration for sandboxes that were running during a service restart?

Yes. The `_restore_existing_sandboxes` function in [`server/src/services/docker.py`](https://github.com/alibaba/OpenSandbox/blob/main/server/src/services/docker.py) (lines 23-62) rebuilds all expiration timers when the service restarts. It iterates through existing containers, checks their `expires_at` timestamps against the current time, and immediately triggers `_expire_sandbox` for any sandboxes that expired while the service was offline. This mechanism ensures TTL enforcement remains reliable across service restarts and crashes.

### How can I detect that a sandbox was terminated due to TTL expiration versus other causes?

Query the sandbox status via `GET /v1/sandboxes/{sandboxId}`. When TTL expiration triggers termination, the API returns `status.state: "Terminated"` and `status.reason: "ttl_expiry"`, as defined in [`specs/sandbox-lifecycle.yml`](https://github.com/alibaba/OpenSandbox/blob/main/specs/sandbox-lifecycle.yml). This distinguishes TTL-driven termination from manual stops (which may use `"user_requested"`) or error conditions (which use specific error codes). The explicit `ttl_expiry` reason code allows applications to distinguish between expected lifecycle expiration and failure scenarios.