Docker Compose for AI Agent Deployment: A Minimal Production Setup

Docker Compose for AI Agent Deployment: A Minimal Production Setup

How to build a Docker Compose setup for an AI agent with named volumes, file-based secrets and healthchecks, so restarts and backups are predictable.

••

Want to apply this in your company?

Book a call with Juan →

Docker Compose for AI Agent Deployment: A Minimal Production Setup

A production-ready Docker Compose setup for an AI agent needs a compose file with pinned image versions, a named volume for state, secrets kept out of the image, healthchecks that show whether the agent is fit to work, and an explicit restart policy. Backup is a tested routine, not a hope. With these pieces in place, restarting the server or updating an image stops being an unpredictable event.

Key takeaways

  • Start with few services: the agent, a database and a backup job. Every extra service needs a clear reason.
  • Keep configuration (read-only), state (named volume) and secrets (files mounted in memory, outside the image and the repository) separate.
  • A good healthcheck verifies what the agent controls, such as configuration and database access, and does not call the model provider on every interval.
  • Automatic restart is not a substitute for monitoring: Docker marks a container as unhealthy but does not restart it for that reason.
  • A backup only exists once a restore has been tested in another environment, with retention and access rules defined.

Which services belong in a minimal setup?

The temptation is to build a full architecture on day one: queue, cache, vector database, dashboard, proxy, orchestrator. For an agent that serves one real workflow (for example, answering questions and handing cases to a person), three pieces are usually enough.

  • agent: the agent process, with its code, allowed tools and decision log.
  • db: a relational database (PostgreSQL is a common choice) for conversations, workflow state and decision logs.
  • backup: a service that only runs on demand, using a Compose profile, to produce copies of the database and the state files.

If the agent has to receive webhooks, for example from a WhatsApp provider, add a reverse proxy in front, and let only the proxy publish ports on the server. The database should never have a ports section. It talks to the agent over an internal Compose network, which removes a whole category of accidental exposure. If you plan to expose an agent gateway to the internet, review the OpenClaw Gateway security checklist before opening any port.

A few small decisions in the compose file matter more than they look:

  • Pin image versions to a specific tag, never latest. Updating becomes your decision, not a surprise from a pull.
  • Set restart: unless-stopped for the agent and the database. The container comes back after a crash or a server reboot, but respects a manual stop.
  • Configure log rotation on the json-file driver (max-size and max-file) so the disk does not fill up silently.
  • Run the agent as an unprivileged user, with a read-only filesystem where possible and tmpfs for temporary directories.
  • Set memory and CPU limits on the agent service, so a problem in the agent does not take down the database.

How do you organize volumes without losing the agent's state?

The practical rule is to separate three kinds of data and treat each one differently.

  • Code and dependencies: these live in the image. Do not bind-mount the code directory over it in production.
  • Configuration: prompt files, the allowed tool list and limits. Mount them as read-only bind mounts, versioned in the repository.
  • State: database data, memory files, local queues and decision records. These go in named volumes managed by Docker.

State is what you cannot afford to lose. If the agent stores conversation history, human approvals and decision logs, that material must survive an image change, a docker compose down and a server migration. Named volumes survive these events, as long as you do not use the option that removes volumes along with down. Write that into the operating procedure, because it is a common mistake when people are in a hurry.

Docker's documentation on volumes describes how to back up and restore a volume using a temporary container. For the database, though, copying files while it is running is not the safest option. Prefer a logical dump (pg_dump in PostgreSQL), which produces a consistent file while the database keeps serving requests.

If the plan includes running a local model next to the agent, the design changes: model weights are large, sit in their own volume and do not need to be in the daily backup. The article on running Ollama local models in a company covers that case in detail.

How do you handle secrets without exposing keys?

The model API key, the WhatsApp token, the database password and the webhook secret are the typical items. There are three places they must not be: inside the image, inside the repository and inside logs.

Compose has a built-in mechanism for this. You declare secrets in the top-level secrets section, pointing each one to a file on the server, and list in each service which secrets it may read. Inside the container, each secret appears as a file under /run/secrets, with the name you defined. Only the listed services can see each one.

The catch is that the application has to read the value from the file. Many official images already accept variables with the _FILE suffix (PostgreSQL accepts POSTGRES_PASSWORD_FILE, for example). In the agent's code, the pattern is simple: if the variable with the _FILE suffix exists, read the file; otherwise use the plain variable, for local development only.

Practices that fit into daily work:

  • Secret files stay outside the repository, with read permission limited to the user who operates the server.
  • The .env file is for non-sensitive values, such as the environment name and log level. It is not a vault.
  • Each service receives only the secrets it needs. The backup service does not need the model key.
  • A written procedure exists for rotating each secret, including who does it and how to confirm the agent came back healthy.
  • The agent never prints secrets or the full content of requests in its logs.

It is also worth remembering that a secret inside the container does not protect you from a manipulated agent. If the agent can be induced to take actions it should not, the problem lies in tool permissions and untrusted input, a topic covered in prompt injection protection for AI agents and in the OWASP Top 10 for LLM applications.

Healthchecks and restarts: what actually happens when something fails?

A healthcheck is a command that Docker runs inside the container at regular intervals. The result sets the state: starting, healthy or unhealthy. The main fields are test, interval, timeout, retries and start_period, the last of which gives the process time to start without counting failures.

Two uses are valuable.

The first is startup order. With depends_on and the service_healthy condition, the agent only starts after the database responds. For the database, the test is usually the pg_isready utility. Docker's documentation on startup order reminds you that the application itself should tolerate slow dependencies, so the agent should also retry the connection with increasing wait times instead of dying on the first failure.

The second is diagnosis. An unhealthy state shows up in docker compose ps and can feed an alert. But in plain Compose, Docker does not restart a container just because it is unhealthy. The restart policy covers the case where the process exits. So two things work better together: the agent should exit the process when it reaches an unrecoverable state, and the server should have a simple monitor that notifies a person when something stays unhealthy for too long.

What should the agent's health endpoint check?

  • The configuration was loaded and the secrets were read.
  • The database answers a simple query.
  • Free disk space on the state volume is above a minimum.
  • The queue of pending messages is not stalled beyond an acceptable time.

What it should avoid: calling the model provider on every check. That adds cost, consumes usage limits and turns a brief external instability into unnecessary restarts. Track provider availability elsewhere, with metrics and logs (the observability concepts in the OpenTelemetry documentation help separate signals). And keep step, time and spending limits inside the agent itself, as described in AI agent infinite loop: step limits, time and budget caps, because a healthy container can be healthily overspending.

How do you make backup and restore predictable?

Predictable means you know what is copied, when, where it goes and how long it takes to come back. A lean design:

  • The backup service sits under a Compose profile, so it does not start with the rest.
  • A scheduler on the server itself (cron or a systemd timer) runs docker compose run --rm backup at a fixed time.
  • The service produces a database dump and a compressed archive of the state volumes that matter, with the date in the file name.
  • The result is encrypted and sent off the server, to separate storage.
  • A retention rule deletes old copies, and an alert fires when the backup fails or does not run.

Because support conversations contain personal data, the backup is part of data processing under LGPD. That calls for defining who has access, how long the copy is kept and where it lives. If the storage is in another country, the discussion of international data transfer under LGPD when using AI providers comes into play. The ANPD is the official reference for following regulatory guidance.

The step most often skipped is the restore. Set a recurring day to restore the latest copy in a separate environment, start the agent pointing at it and check that the conversations and decision logs are there. Note how long it took. That record is worth more than any promise about the backup.

Step by step: deploying and operating

A deployment sequence that works for a first agent:

  • Write the compose file with the agent and db services, the internal network, the named volumes and pinned image versions.
  • Create the secret files on the server, with restricted permissions, and declare them in the secrets section.
  • Implement the agent's health endpoint and configure each service's healthcheck with a realistic start_period.
  • Set depends_on with service_healthy and define restart: unless-stopped.
  • Configure log rotation and resource limits.
  • Add the backup service with a profile, run it once manually and check the generated files.
  • Schedule the routine, set up the failure alert and run the first test restore.
  • Simulate three events: a server restart, a database outage and a secret rotation. Record what happened in each.
  • Only then turn on real traffic, with handoff to a person when the agent does not know or cannot act.

Illustrative scenario: a small company puts an agent in charge of answering frequent questions on WhatsApp. In the middle of the night, the server reboots after a system update. With restart defined, the database and the agent come back; the agent waits for the database to become healthy and resumes the queue of pending messages, because the state was in a named volume. If the database did not come up, the unhealthy state would trigger the alert, and the team would have a restore procedure already tested. Without these pieces, the same night would end with lost messages and nobody sure which copy to use.

How does this connect to the deployment method?

The compose file is the technical part of a larger process. In the method we follow, we first map the current process and the baseline, so we know what the agent will touch. Then we limit data and actions: which secrets exist, which tools the agent can call and what requires human approval. Next we pilot one real workflow, with logs and an agreed success metric. Finally, we hand over operations with the restart, backup and restore procedure documented, and the team decides whether to expand, adjust or stop.

Compose matters mostly in the limit and pilot stages, because it turns security and operations decisions into files that can be reviewed. When the environment gets more complex, with several agents or cluster orchestration, it is worth reassessing the tool. For one agent and one workflow, simplicity is usually an operational advantage.

Frequently asked questions

Is Docker Compose suitable for production or only for development? It works for production in single-server setups with a few services, as long as you pin versions, handle secrets, configure healthchecks and have a tested backup. It does not provide high availability across servers on its own. If the business needs continuity even when one machine fails, you need a different architecture.

Can I use environment variables to store the model API key? You can, but it is the most fragile option, because variables show up in container inspections, diagnostic dumps and sometimes logs. Compose secrets, mounted as files, reduce that risk and let you control which service reads each value. Use environment variables only for non-sensitive configuration.

What should go into an AI agent's backup? The database, the state files and the decision records, plus the configuration files that live in the repository. Images and public model weights can usually be downloaded again. Secrets should be kept by a separate, controlled process, not alongside the data backup.

How often should I test the restore? Set a recurring routine, and also test after any relevant change, such as a database version upgrade or a change in the volume layout. The goal is to know the real recovery time and confirm that the agent works with the restored data. Record each test, because that history shows whether the procedure is still reliable.

Sources