Appearance
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.

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
| Option | Good for | Costs you | When to pick it |
|---|---|---|---|
| A small cloud VM (DigitalOcean, Hetzner, EC2) | Almost every agent in this course | ~$5–20/month, you manage the OS | Default choice, full control, predictable cost, easy to debug |
| A managed container platform (Fly.io, Railway, Render) | Teams who don't want to manage a server | Slightly more per month, less control | You want git push to deploy and don't want to think about the OS |
| Serverless functions (Cloud Run, Lambda) | Short-lived, request-triggered agent tasks | Cold-start latency, harder to run a long-lived process | Your 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.targetAfter 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.serviceRestart=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
.envEnvironmentFile=/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
| Mistake | How you notice it | The fix |
|---|---|---|
| Agent runs in a terminal session | It stops the moment you close your laptop or SSH disconnects | Run it under systemd (or your platform's process manager), not a raw terminal |
| API key committed to git | It shows up in git log -p or a GitHub secret-scanning alert | Rotate the key immediately, move it to .env, add .env to .gitignore |
| Process crashes and stays down | No alert fires, you find out when a user reports the agent is unresponsive | Restart=on-failure in systemd, plus a /health endpoint an uptime monitor checks |
| Out of memory on a small VM | Process 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. production | Works locally, breaks in production with a missing-key error | Keep 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.