# Deploy your first agent

Deploying an agent means moving the same agent loop you ran on your laptop into a
place that runs without you watching it. Three things change: where the code
runs, how it gets its secrets and tools, and how you bound what it is allowed to
do when nobody is there to click "approve".

On your machine the agent borrows a lot for free. It uses your shell, your files,
your API key sitting in your shell profile, and you, the human, approving each
risky step. A server has none of that. The job of a first deployment is to hand
the agent each of those things on purpose, and nothing more.

## What changes when an agent leaves your laptop?

Your laptop run and your deployed run are the same agent loop. The loop calls the
model, the model asks to run a tool, the tool runs, the result goes back, repeat
until done. The [Claude Agent SDK](/guides/claude-agent-sdk) runs that loop for
you in either place. What differs is everything around the loop.

So a deployment is four decisions: pick a host, supply the secret, fix the tool
list, and capture the output. Take them one at a time.

## Where does a deployed agent run?

You have three broad options. Pick the smallest one that fits.

| Host | What it is | Good first fit |
| --- | --- | --- |
| A container | Your code in an image you run anywhere | Yes, most teams start here |
| A CI job | The agent runs inside a pipeline step | Good for code tasks on a repo |
| Managed agents | Anthropic runs the loop and the sandbox | When you do not want to run infra |

A container is the common starting point because it is portable and you control
it. You build an image, set environment variables, and run it. The agent works on
files and services inside that container, not on your laptop.

The other end of the spectrum is letting Anthropic host the loop for you. With
[managed agents](/guides/claude-managed-agents) the loop and a per-session
sandbox run on Anthropic infrastructure, and your app sends events and reads back
results over a REST API. A common path is to prototype with the SDK locally, then
move to managed agents once you do not want to operate sandboxes yourself.

For a first deployment, start with a container running the SDK. Everything below
assumes that.

## How does a deployed agent get its API key?

On your laptop the agent reads `ANTHROPIC_API_KEY` from your shell. The SDK does
the same thing on a server: it reads the key from an environment variable.

```bash
# the SDK reads this from the environment, same as on your laptop
export ANTHROPIC_API_KEY=your-api-key
```

The change is where that value comes from. Hardcoding the
key is the most common deployment mistake, and it is a real one. Never put the
key in your `Dockerfile`, your pipeline YAML, or any file you commit. Anyone who
can read the file, or the image layers, or the git history, then has your key.

Instead, inject it at run time from whatever your host already provides:

- In CI, use the pipeline's secret store (for example a repository or
  organisation secret) and expose it to the step as an environment variable.
- In a container platform, set it as a secret environment variable on the
  service, not baked into the image.
- In a cloud, read it from the platform's secrets manager at start-up.

If you run Claude on a cloud provider instead of the Anthropic API directly, you
set a different flag and use that cloud's normal credentials rather than an
Anthropic key. For example, Amazon Bedrock uses `CLAUDE_CODE_USE_BEDROCK=1` plus
your AWS credentials, and Google Vertex AI uses `CLAUDE_CODE_USE_VERTEX=1` plus
your Google Cloud credentials. The principle is identical: the secret lives in the
platform, the agent reads it at run time, nothing is committed.

A practical habit: rotate the key on a schedule, and rotate it immediately if you
think it leaked. A deployed agent runs unattended, so a leaked key can be abused
for a long time before anyone notices.

## How does a deployed agent get its tools?

A [tool](/guides/give-your-agent-tools) is anything the agent can do beyond talking: read a file, run a shell
command, search the web, call your service. On your laptop you gave it broad
access because you were watching. Unattended, you give it the smallest set the job
needs.

You declare the allowed built-in tools as a plain list. The names are the same as
in Claude Code: `Read`, `Write`, `Edit`, `Bash`, `Glob`, `Grep`, `WebSearch`,
`WebFetch`. A read-only reviewing agent, for example, gets only the tools that
read:

```json
{
  "allowedTools": ["Read", "Glob", "Grep"]
}
```

That single list is your strongest safety control. An agent that has never been
given `Bash` or `Write` cannot run a command or change a file, no matter what the
model decides it wants to do. Start from nothing and add only what the task
proves it needs.

When the agent needs to reach a system you own, a database or your own API, you do
not hand it raw credentials. You expose that system as an [MCP](/guides/mcp-fundamentals)
server and let the agent call it through that. MCP is a uniform interface for
tools and data, so the same wiring works locally and in production. You list the
servers the agent may connect to as config:

```json
{
  "mcpServers": {
    "store-db": {
      "command": "npx",
      "args": ["-y", "@your-org/store-db-mcp"]
    }
  }
}
```

In a deployment, treat an MCP server like any other dependency: it needs its own
secrets (a database URL, an API token), and those come from the environment too,
never from a committed file. If you connect untrusted or third-party MCP servers,
read [MCP security basics](/guides/mcp-security-basics) first, because a tool the
agent can call is a tool an attacker can try to reach through the agent.

## How do I limit what an agent can do when nobody is watching?

On your laptop, a permission prompt is your safety net: the agent pauses and asks
before doing something risky. A server has no human to answer that prompt. So an
unattended agent must never depend on one.

The SDK ships permission modes that set how cautious the loop is:

- **default** · ask before anything risky
- **acceptEdits** · auto-approve file edits, still gate the rest
- **plan** · explore and propose, change nothing
- **bypassPermissions** · skip prompts entirely

For a deployment, the combination that matters is a tight `allowedTools`
allow-list plus a mode that does not hang waiting for input. The `default` mode
is fine when you pair it with a permission callback, a function the SDK calls for
anything not already resolved by your allow-list, so it returns a yes or no in
code instead of waiting for a person.

Never use `bypassPermissions` outside an isolated sandbox. It removes
every guardrail at once, which is exactly what you do not want on a host that
touches real systems. If you think you need it, the safer answer is usually a
shorter `allowedTools` list and a sandbox with no network and no access to
anything outside the task.

The principle is fail closed. Anything you did not explicitly allow should be
denied automatically, not left to a prompt that nobody will answer.

## The smallest safe first deployment

Here is a first deployment that is useful and hard to get wrong: a
read-only agent that reviews a codebase and writes a report. It cannot edit
files, cannot run commands, and cannot reach the network beyond the model. The
worst case is a wrong opinion in a text file.

You can prove the exact shape of the run from the CLI before you wrap it in a
service. Claude Code runs the same loop in one shot with `-p` (print mode), and a
trailing tool allow-list:

```bash
# one-shot, read-only, prints the result and exits
claude -p "Review the auth module for security issues. Write findings to review.md" \
  --allowedTools "Read,Glob,Grep,Write"
```

Print mode is the
bridge between your laptop and a server: it takes a prompt, runs to completion,
and exits with output you can capture. That is exactly what a host needs.

Use a tight, explicit prompt so the unattended run has no room to wander:

```text
You are reviewing a codebase. Do not modify any files except review.md.

Steps:
1. Read the files under src/auth.
2. List concrete security risks you can see, with file and line.
3. For each risk, say why it matters and one fix.
4. Write the result to review.md as a short markdown report.

Rules:
- Do not run shell commands.
- If you are unsure about a file, say so rather than guess.
```

When that one-shot behaves the way you want, you have your deployment: the same
prompt, the same allow-list, the same key from the environment, now running on a
host on a trigger you choose. Wrap it so the run is reproducible:

```bash
# build an image that contains your code, not your secret
docker build -t store-review-agent .

# run it, injecting the key at run time from your shell or CI secret
docker run --rm -e ANTHROPIC_API_KEY store-review-agent
```

Notice the key is passed in at `docker run`, not built into the image. The image
is safe to push to a registry; the secret never leaves the run.The `-e ANTHROPIC_API_KEY` form with no `=value` only forwards the variable if it is set in the calling shell or CI environment; when it is unset, Docker passes nothing and the container starts with an empty key, so the agent fails on its first model call with a confusing 401 rather than a clear "key missing" error. Verify the variable is exported in the run context first, or pass `-e ANTHROPIC_API_KEY="$ANTHROPIC_API_KEY"` and let the run fail loudly if it is blank.

## Watch the run

Unattended does not mean unobserved. The agent loop emits a stream of structured
messages: each step it takes, each tool it calls, and a final result that
includes the outcome and the cost. Send that stream to your logs.

You read those logs for three things: did it finish, what did it touch, and what
did it spend. The final result message carries token usage, which is how you keep
an eye on [cost](/guides/token-usage-and-cost) before a runaway loop becomes an
expensive surprise. A sensible first deployment also has a hard stop: a maximum
number of turns or a wall-clock timeout, so a confused agent halts instead of
looping forever.Set the turn limit before the first run, not after. On a laptop you notice a stuck loop and hit Ctrl-C; an unattended agent with no cap can spend hours and a large bill calling the model in circles before anyone checks the logs. Give it a low ceiling (say 15 turns) and raise it only once you have seen real runs finish under it.

## A checklist before you ship

Run through this before the first unattended run:

- [ ] The key is injected at run time, not in the image, the YAML, or git.
- [ ] `allowedTools` lists only what the task needs, nothing more.
- [ ] No `bypassPermissions` outside a sandbox; the run fails closed.
- [ ] Every external system is reached through MCP, with its own injected secrets.
- [ ] There is a turn limit or timeout so the loop cannot run forever.
- [ ] The message stream goes to logs you will read.
- [ ] You tested the exact prompt and allow-list as a one-shot first.

## Where to go next

A read-only reviewer is a real deployment and a safe place to learn the moving
parts. Once it runs cleanly, you widen it on purpose: add `Edit` for an agent
that proposes patches, add an MCP server for an agent that reads your database,
add a permission callback for the few decisions you want to make in code.

If you want stronger guardrails as you widen the blast radius, read
[hooks and guardrails](/guides/hooks-and-guardrails) for code that runs at fixed
points in the loop. And if running your own host stops being worth it, that is the
signal to look at [managed agents](/guides/claude-managed-agents), where the
infrastructure becomes someone else's problem and you keep the prompt, the tools,
and the rules.
