SokkoSokko
← Back to blog

How to Containerize an AI Agent with Docker (Step by Step)

Sokko Team8 min read

Why containerize an AI agent with Docker at all

Your agent works on your machine. Then a teammate clones it and it breaks, because they have a different Python version, a missing system library, or a dependency that resolved differently. To containerize an AI agent with Docker is to end that whole class of problem: the image pins your runtime, your OS libraries, and your dependencies together, so the agent that runs on your laptop runs byte-identically in the cloud.

This is a how-to. By the end you will have a Dockerfile that builds a reproducible image for an agent, with secrets and persistent state handled correctly, ready to run anywhere that runs containers. The example uses a Python agent, but the shape is the same for Node or Go.

Containerizing does two jobs at once. It makes the environment reproducible, and it makes the agent portable, one image runs on a PaaS, a VM, or Kubernetes with no edits. That portability is what makes the later step of moving your local AI agent to the cloud a push instead of a migration.

Step 1: a working Dockerfile

Start with the smallest Dockerfile that runs the agent, then harden it. Here is a solid baseline for a Python agent:

# Pin a specific version, not "latest", for reproducibility
FROM python:3.12-slim

# Create a non-root user; agents should not run as root
RUN useradd --create-home --uid 1000 agent
WORKDIR /app

# Install deps first, in their own layer, so code changes do not
# bust the dependency cache on every build
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Now copy the code
COPY . .

# Drop privileges
USER agent

# The agent is a long-lived process, not a one-shot command
CMD ["python", "-u", "agent.py"]

Three details in there matter more than they look:

  • Pin the base image (python:3.12-slim, not python:latest). "Latest" changes under you and breaks reproducibility, the exact thing you are here to fix.

  • Copy requirements.txt before the code. Docker caches layers. Put the slow, rarely-changing step (installing deps) above the fast, often-changing step (copying code) so rebuilds are quick.

  • Run as a non-root user. An agent executes model-decided actions. If it is compromised or misbehaves, you want it confined to an unprivileged user, not root.

Step 2: build and run it locally

# Build the image, tagged with a name and version
docker build -t my-agent:0.1.0 .

# Run it, passing config as environment variables
docker run --rm \
  -e OPENAI_API_KEY="$OPENAI_API_KEY" \
  my-agent:0.1.0

If it runs here, it runs anywhere Docker runs. That is the payoff. But two things in that command are placeholders for problems you have to solve properly: secrets and state. Passing a key on the command line is fine for a local test and wrong for production.

Step 3: handle secrets without baking them in

The cardinal rule: never put a secret in the image. Anyone who pulls the image can read anything baked into it, so an API key in the Dockerfile or committed .env is a leaked key. Inject secrets at runtime instead.

# Local dev: an env file that is gitignored, never committed
docker run --rm --env-file .env.local my-agent:0.1.0
# Never do this: a secret baked into a build layer, readable by anyone
# RUN echo "OPENAI_API_KEY=sk-..." >> /app/.env   <-- leaked forever

For anything beyond local dev, the runtime injects secrets from a real secrets store, scoped per agent and rotatable without a rebuild. That is one of the concerns that separates a bare container from a full AI agent runtime: the container is the package, the runtime supplies the secrets safely.

Step 4: persist state on a volume, not in the container

A container's filesystem is ephemeral. Anything the agent writes inside the container is gone when it restarts. For an agent that keeps task progress, memory, or a workspace, that is a data-loss bug waiting to happen. The fix is a mounted volume that lives outside the container's lifecycle:

docker run --rm \
  --env-file .env.local \
  -v agent-data:/workspace \
  my-agent:0.1.0

Now the agent writes to /workspace, which survives restarts and redeploys. Write task state and any memory there, never to /tmp or the container root. This is the same persistence requirement behind persistent memory for AI coding agents: memory only sticks if it lives on storage that outlives the process.

Step 5: keep the image small and reproducible

A few practices that pay off once the image goes to production:

  1. Use a slim or multi-stage build. A -slim base or a multi-stage build that discards build tools keeps the final image small, which means faster pulls and less attack surface.

  2. Add a .dockerignore. Exclude .git, local env files, and caches so they never enter the build context. This also stops you accidentally copying a secret into the image.

  3. Pin dependency versions. Pin in requirements.txt (or lockfile) so a build next month produces the same image as today.

  4. Do not run as root. Covered above; it is worth repeating because it is the most commonly skipped one.

The Docker build best practices guide goes deeper on layer caching and multi-stage builds if you want to optimize further.

A reproducibility checklist

Before you call the image done, confirm:

  • Base image is pinned to a specific version, not latest.

  • Dependencies are pinned in a lockfile or requirements.txt.

  • No secrets are baked into any layer.

  • A .dockerignore excludes .git, env files, and caches.

  • The agent runs as a non-root user.

  • State is written to a mounted volume, not the container filesystem.

  • The image builds clean on a machine that is not yours (or in CI).

That last one is the real test of reproducibility: if it builds and runs on a fresh machine, you have escaped "works on my machine" for good.

From image to production

Once you have containerized an AI agent with Docker, running it in production is mostly about handing the image to something that will supervise it: restart it on crash, mount its volume, inject its secrets, and give it a network identity. For a custom image like the one you just built, that means a VM or a container platform you run yourself, with restart policies, volumes, and secrets wired up the way this post describes.

If what you actually need is an always-on assistant rather than custom framework code, Sokko skips the image-building step entirely: you deploy one of four ready-made agent runtimes (OpenClaw, Hermes, Paperclip, OpenSRE) from the dashboard in a couple of clicks, bring your own model keys or use Sokko credits, and the runtime concerns from the checklist (durable storage, secure secret storage, automatic restart-on-crash) are handled as defaults. The deploying your first agent walkthrough shows the flow.

Multi-stage builds for smaller, safer images

Once the basic image works, a multi-stage build is the upgrade that pays off most. It uses one stage to install and compile, then copies only the finished artifacts into a clean final stage, so build tools never ship to production:

# Stage 1: build dependencies
FROM python:3.12-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt

# Stage 2: the runtime image, without build tooling
FROM python:3.12-slim
RUN useradd --create-home --uid 1000 agent
WORKDIR /app
COPY --from=builder /install /usr/local
COPY . .
USER agent
CMD ["python", "-u", "agent.py"]

The final image carries the installed packages and your code, and nothing from the build step. Smaller image, faster pulls, and less attack surface, because a compiler an attacker could use is not sitting in your production container.

Debugging a container that will not start

When a freshly containerized agent fails to run, the cause is almost always one of a short list. Work through it in order:

  1. Read the logs first. docker logs <container> shows the actual error. Guessing before reading the logs wastes time.

  2. Check it is a long-lived command. If the container exits immediately, your CMD probably ran and finished. An agent should be a loop that stays up, not a script that returns.

  3. Confirm the secrets arrived. A missing API key often shows as an auth error deep in a stack trace. Print which env vars are set (never their values) at startup to catch this fast.

  4. Verify the volume mount. If state is not persisting, check the -v flag actually mounts where the agent writes. A typo in the path silently writes to the ephemeral filesystem instead.

  5. Test the network. If the agent cannot reach an API, confirm the container has outbound access and any required egress rules are in place.

Nine times out of ten it is one of these, and the logs point straight at it.

The takeaway

To containerize an AI agent with Docker, pin your base image, install dependencies in a cached layer, run as a non-root user, keep secrets out of the image, and write state to a volume. Get those right and you have a reproducible, portable agent environment that runs the same on your laptop as it does in production. The container is not the whole story, it still needs a runtime to keep it alive, but it is the foundation everything else builds on.