Skip to content

Deploying Your Agent

A working agent on your laptop isn't a shipped product. This module covers the practical last mile: getting an agent process running somewhere that stays up, keeping your API keys out of your code, and knowing what to check when it goes down.

Owl mascot

What you'll learn

  • Picking a host without overthinking it, a VM is enough for almost everyone
  • Keeping API keys and secrets out of your repo, correctly
  • What "it's running" actually needs to mean before you trust it

1. The problem

An agent that only runs when you personally start it in a terminal isn't deployed, it's a demo. Shipping it means three things have to be true at once: it keeps running without you watching it, it can restart itself if it crashes, and none of your API keys are sitting in plain text where anyone with repo access can read them.

None of this requires GPU infrastructure. Almost every agent built in this course calls a hosted model API (Claude, Gemini) rather than running its own model, so "deploying the agent" means running a normal Python process reliably, not managing a GPU fleet.

2. Options & when to use each

OptionGood forCosts youWhen to pick it
A small cloud VM (DigitalOcean, Hetzner, EC2)Almost every agent in this course~$5–20/month, you manage the OSDefault choice, full control, predictable cost, easy to debug
A managed container platform (Fly.io, Railway, Render)Teams who don't want to manage a serverSlightly more per month, less controlYou want git push to deploy and don't want to think about the OS
Serverless functions (Cloud Run, Lambda)Short-lived, request-triggered agent tasksCold-start latency, harder to run a long-lived processYour agent only needs to react to occasional webhook events, not run continuously

For a single agent serving one team or product, a small VM is the right default, it's what "Deploying Agents" in the Foreman capstone uses, and it's the cheapest thing to reason about when something breaks.

3. Build it

Keep the process running: systemd

Don't run your agent in a tmux session and call it deployed, if the VM reboots, it's gone. Use systemd so the OS restarts it for you.

ini
# /etc/systemd/system/agent.service
[Unit]
Description=Foreman agent
After=network.target

[Service]
Type=simple
User=deploy
WorkingDirectory=/home/deploy/agent
EnvironmentFile=/home/deploy/agent/.env
ExecStart=/home/deploy/agent/.venv/bin/python run_agent.py
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target

After writing your unit configuration file to /etc/systemd/system/agent.service, reload the systemd daemon to register it and enable the service to start immediately on boot:

bash
sudo systemctl daemon-reload
sudo systemctl enable --now agent.service
sudo systemctl status agent.service

Restart=on-failure is the part that matters most: if the process crashes, an unhandled exception, an OOM kill, systemd brings it back up within seconds instead of leaving your agent offline until you notice.

Keep secrets out of your code

Never hardcode an API key, and never commit a .env file.

bash
# .env, lives only on the server, never in git
ANTHROPIC_API_KEY=sk-ant-...
GEMINI_API_KEY=...
DB_URL=postgresql://...
# .gitignore
.env

EnvironmentFile=/home/deploy/agent/.env in the systemd unit above loads these as environment variables for the process, your code reads them with os.environ["ANTHROPIC_API_KEY"], never a string literal. If you're on a team, use your platform's secret manager (GitHub Actions secrets, DigitalOcean App secrets) instead of hand-copying .env files between people.

A minimal health check

Give yourself one way to ask "is this actually working" from outside the box:

python
from fastapi import FastAPI
import time

app = FastAPI()
started_at = time.time()

@app.get("/health")
def health():
    return {"status": "ok", "uptime_seconds": round(time.time() - started_at)}

Verify that the health check service is responsive by querying the /health endpoint from the terminal:

bash
curl http://your-server:8000/health
# {"status":"ok","uptime_seconds":4213}

Point an uptime monitor (see Observability) at this endpoint so you find out about a crash from an alert, not from a user complaint.

4. What goes wrong

MistakeHow you notice itThe fix
Agent runs in a terminal sessionIt stops the moment you close your laptop or SSH disconnectsRun it under systemd (or your platform's process manager), not a raw terminal
API key committed to gitIt shows up in git log -p or a GitHub secret-scanning alertRotate the key immediately, move it to .env, add .env to .gitignore
Process crashes and stays downNo alert fires, you find out when a user reports the agent is unresponsiveRestart=on-failure in systemd, plus a /health endpoint an uptime monitor checks
Out of memory on a small VMProcess gets silently killed (OOMKilled in logs, or systemd shows exit code 137)Size the VM for your actual peak memory use, or add a swap file as a buffer
Different environment variables in dev vs. productionWorks locally, breaks in production with a missing-key errorKeep a .env.example listing every required variable with no real values, and diff against it before deploying

5. Confirm it worked

To confirm that the process supervisor configuration behaves correctly under boots, reboots, and simulated process crashes, run these verification commands on the server:

bash
# Confirm the service survives a reboot
sudo systemctl is-enabled agent.service   # should print: enabled

# Confirm it restarts after a crash
sudo systemctl kill -s SIGKILL agent.service
sleep 6
sudo systemctl status agent.service       # should show: active (running), started ~5s ago

# Confirm the health endpoint responds from outside the box
curl -f http://your-server:8000/health || echo "FAILED"

If all three pass, your agent is deployed, not just running.

Next step: proceed to Exposing Agents to Users to give real users a way to reach this agent.