> ## Documentation Index
> Fetch the complete documentation index at: https://docs.xpander.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Containers

> Ship your @on_task handler as a managed container in xpander cloud

When you deploy, xpander builds a Docker image from your project, runs it, and routes incoming tasks to your `@on_task` handler.

## Deploy

```bash theme={"dark"}
xpander agent deploy
```

Some notes:

* **Each deploy creates a new immutable version.** The previous version stays available for instant rollback.
* **The first deploy takes a couple of minutes.** Subsequent deploys are faster because layer caching kicks in.
* **Build errors surface in the deploy output.** Missing dependencies and syntax errors in `xpander_handler.py` show up there before the new version goes live.

Stream logs from the running container after deploy:

```bash theme={"dark"}
xpander agent logs
```

## Manage environment variables and secrets

The container loads `.env` at runtime. For variables that are the same across all environments, put them in `.env` and they ship with the deploy.

For secrets you don't want bundled into the image:

```bash theme={"dark"}
xpander secrets-sync
```

This uploads the variables in your local `.env` to xpander's secret store and injects them into the container at runtime without bundling them into the image. Run it whenever you've added or rotated a secret. Don't commit `.env` to source control either way.

## Restart, stop, and roll back

```bash theme={"dark"}
xpander agent restart      # Restart the running container without a redeploy.
xpander agent stop         # Stop the container entirely.
xpander agent deploy --version <id>   # Roll back to a prior immutable version.
```

Restart is useful when the container's state has gotten weird (cached resource leaks, stuck connections) but the code is fine. The Workbench also has a rollback UI if you prefer not to use the CLI.

## Lifecycle hooks

Containers support startup and shutdown hooks. Use them for resource setup that should happen once, before tasks start arriving:

```python highlight={3-4,8-9} theme={"dark"}
from xpander_sdk import on_boot, on_shutdown

@on_boot
async def warm_up():
    # Connect to internal services, prime caches, validate env.
    ...

@on_shutdown
async def clean_up():
    # Flush metrics, close DB connections, save state.
    ...
```

Some notes:

* **`@on_boot` failures abort startup.** Use them for things that genuinely need to succeed before the agent runs, for example: database connectivity, required env validation.
* `@on_shutdown` failures are logged but don't block shutdown.
* Multiple hooks of the same type run in registration order.

## CI/CD

The CLI is designed to fit into pipelines. Authenticate via `XPANDER_API_KEY` and pass `--confirm` to skip prompts:

```yaml highlight={3-5} theme={"dark"}
steps:
  - run: npm install -g xpander-cli
  - run: xpander agent deploy --agent-id "${{ secrets.XPANDER_AGENT_ID }}" --confirm
    env:
      XPANDER_API_KEY: ${{ secrets.XPANDER_API_KEY }}
```

`xpander secrets-sync` works the same way. Pipe in a `.env` from your secrets manager and the keys reach the container without sitting in your repo.

For multi-environment setups (staging + production), use CLI profiles:

```bash theme={"dark"}
xpander login --profile staging
xpander login --profile production
xpander agent deploy --profile production --confirm
```

Agent IDs differ between environments by design. Memory, sessions, and tool configurations are scoped per agent, so a mistake in staging can't leak into production data.

## Troubleshooting

<AccordionGroup>
  <Accordion title="Deploy fails with a build error">
    Build errors print in the deploy output before the new version goes live. The most common causes are missing packages in `requirements.txt`, syntax errors in `xpander_handler.py`, or a Dockerfile command that fails (e.g., `apt-get install` for a package that doesn't exist). Fix the issue and redeploy.
  </Accordion>

  <Accordion title="Container starts but tasks don't arrive">
    Check that the agent is published in Agent Studio (look for a Deploy button or pending changes banner). Also confirm the container is running with `xpander agent logs`. If there's an exception during `@on_boot`, the SSE listener never starts.
  </Accordion>

  <Accordion title="`@on_boot` ran but the agent crashes on first task">
    The SSE listener starts after `@on_boot` succeeds, so a crash on first task usually means an unhandled exception in your `@on_task` handler. Stream logs with `xpander agent logs` immediately after invoking to see the traceback.
  </Accordion>

  <Accordion title="Secrets aren't available in the container">
    Run `xpander secrets-sync` after adding or rotating secrets. The sync pushes to xpander's secret store; the container picks them up on the next restart or deploy. You don't need to redeploy the code; just sync and restart.
  </Accordion>
</AccordionGroup>

## Next steps

<CardGroup cols={2}>
  <Card title="Invocation channels" icon="signal" href="/developers/deployment/invocation-channels">
    REST, Slack, MCP, webhooks, cron, and agent-to-agent.
  </Card>

  <Card title="Custom tools" icon="wrench" href="/developers/tools/custom-tools">
    Add Python functions and connectors to your handler.
  </Card>

  <Card title="CLI reference" icon="terminal" href="/developers/cli-reference/overview">
    Every `xpander` command.
  </Card>

  <Card title="Self-hosted Kubernetes" icon="server" href="/self-hosted/index">
    Run the entire stack in your own infrastructure.
  </Card>
</CardGroup>
