How to Deploy an AI Agent 24/7: Docker Setup for an Always-On Server

To run an AI agent 24/7, package it in a Docker container, run it with Docker Compose on an always-on server with restart: unless-stopped, keep API keys in an .env file that never enters git, rotate logs, add a health check, and put hard limits on LLM spending. Each step below comes with files you can copy, written for a Python agent; a Node.js agent follows the same pattern.
"Agent" here means a long-running process that calls an external LLM API, uses tools, and usually talks to people through Telegram, WhatsApp or Discord or runs scheduled jobs. If yours is a plain chat bot, our guide to hosting a Telegram bot 24/7 on a VPS covers the bot-specific parts.
Why the laptop version keeps stopping
Sleep: the agent dies whenever the machine sleeps.
No reachable address: home connections sit behind NAT with changing IPs, so webhooks cannot reach you without a tunnel.
Crashes at 3 a.m.: an unhandled exception ends the process until you notice.
Keys on a personal device and no log history.
An always-on server fixes the first two; the rest is process discipline. For hardware planning by workload, see our breakdown of AI application server requirements.
Step 1: Containerize the agent
Put the code in an agent/ package with main.py (the bot or API entry point) and worker.py (long tasks), pin versions in requirements.txt, then add this Dockerfile:
FROM python:3.12-slim
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1
WORKDIR /app
RUN useradd --create-home --uid 10001 agent
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY --chown=agent:agent . .
USER agent
CMD ["python", "-m", "agent.main"]Copying requirements.txt first keeps dependency layers cached, so code-only rebuilds take seconds. PYTHONUNBUFFERED=1 makes log lines appear immediately instead of sitting in a buffer, and the container runs as an unprivileged user. Add a .dockerignore, otherwise COPY . . bakes your secrets into the image:
.env
.git
__pycache__/
*.pycStep 2: Docker Compose with restarts and health checks
This file runs the agent, a worker for long jobs, and PostgreSQL with pgvector for memory:
x-logging: &default-logging
driver: json-file
options:
max-size: "10m"
max-file: "3"
services:
agent:
build: .
restart: unless-stopped
env_file: .env
depends_on:
db:
condition: service_healthy
healthcheck:
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8080/health', timeout=3)"]
interval: 30s
timeout: 5s
retries: 3
start_period: 20s
logging: *default-logging
worker:
build: .
command: ["python", "-m", "agent.worker"]
restart: unless-stopped
env_file: .env
stop_grace_period: 30s
depends_on:
db:
condition: service_healthy
logging: *default-logging
db:
image: pgvector/pgvector:pg16
restart: unless-stopped
environment:
POSTGRES_DB: agent
POSTGRES_USER: agent
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U agent -d agent"]
interval: 10s
timeout: 5s
retries: 5
logging: *default-logging
volumes:
pgdata:sudo systemctl enable --now docker
docker compose up -d --build
docker compose ps
docker compose logs -f --tail=100 agentWhat each part does, and the traps:
restart: unless-stopped restarts a container that exits and starts it again after a server reboot, as long as the Docker service is enabled. Containers you stopped by hand stay stopped.
The health check only labels a container "unhealthy". Plain Docker Compose does not restart unhealthy containers. Design the agent to exit when it is stuck: have
/healthreport the time of the last successful loop, and let an internal watchdog exit the process if that is older than a few minutes. The restart policy does the rest.depends_on with service_healthy holds the agent until PostgreSQL accepts connections, which avoids a crash loop on boot.
Log limits matter: without
max-size, Docker's default JSON logs grow until the disk is full.
If your server already has PostgreSQL with pgvector installed, you can drop the db service and point DATABASE_URL at it, allowing connections from the Docker bridge network in listen_addresses and pg_hba.conf. More production details are in our Docker Compose production checklist.
Step 3: Keep secrets out of code and git
LLM_API_KEY=replace-with-your-key
TELEGRAM_BOT_TOKEN=replace-with-your-token
POSTGRES_PASSWORD=replace-with-output-of-openssl-rand-hex-24
DATABASE_URL=postgresql://agent:same-password-as-above@db:5432/agent
DAILY_BUDGET_USD=5
MAX_STEPS_PER_TASK=8openssl rand -hex 24
chmod 600 .env
echo ".env" >> .gitignoreCompose reads .env both for env_file and for ${POSTGRES_PASSWORD} in the YAML. A hex password avoids characters that would break the database URL. Use a separate API key per project so you can see its spend and revoke it alone. If a key ever lands in a git commit, rotate it at the provider; deleting the commit is not enough.
Step 4: Guard the LLM bill
Agents loop, and a bug in a tool call can repeat thousands of API requests overnight. Layer the limits:
Per call: set max output tokens and a request timeout.
Per task: cap steps (8 to 10 is plenty for most agents) and total wall-clock time.
Per user: daily message or token quotas on any public bot.
Retries: exponential backoff on 429 and 5xx with a maximum count, never an infinite loop.
Per day: record usage and refuse new calls once the budget is hit.
Last line: spending limits and budget alerts in your LLM provider's dashboard.
CREATE TABLE llm_usage (
id bigserial PRIMARY KEY,
created_at timestamptz NOT NULL DEFAULT now(),
task_id text,
model text NOT NULL,
input_tokens integer NOT NULL,
output_tokens integer NOT NULL,
cost_usd numeric(10,5) NOT NULL
);
-- before each LLM call: stop if today's spend reached the budget
SELECT coalesce(sum(cost_usd), 0) AS spent_today
FROM llm_usage
WHERE created_at >= date_trunc('day', now());Step 5: Queue long tasks instead of blocking
Messaging platforms expect a fast acknowledgement of each webhook and resend if they do not get one, which is how a slow agent ends up answering twice. Receive the message, store a job, acknowledge, and let the worker handle research, scraping or long chains. At small scale PostgreSQL is a perfectly good queue:
CREATE TABLE jobs (
id bigserial PRIMARY KEY,
status text NOT NULL DEFAULT 'queued',
payload jsonb NOT NULL,
attempts integer NOT NULL DEFAULT 0,
created_at timestamptz NOT NULL DEFAULT now()
);
-- each worker claims one job; SKIP LOCKED stops two workers taking the same row
UPDATE jobs SET status = 'running', attempts = attempts + 1
WHERE id = (
SELECT id FROM jobs
WHERE status = 'queued'
ORDER BY created_at
LIMIT 1
FOR UPDATE SKIP LOCKED
)
RETURNING id, payload;Move to Redis when you process many jobs per second. For WhatsApp, connect through the official WhatsApp Business Cloud API; unofficial libraries break the platform's terms and risk the number.
Step 6: Memory and state live in PostgreSQL
Conversation history, user settings, tool results and job status belong in the database, not in process memory, so a restart loses nothing. Send the model only the last few turns plus a running summary, because the full history costs tokens on every call. For long-term knowledge, store embeddings next to your data with pgvector; our pgvector RAG tutorial walks through the schema and queries.
Step 7: Updates and monitoring
git pull
docker compose build agent worker
docker compose up -d agent worker
docker image prune -fCompose recreates only the services you name, so downtime is the few seconds of a restart. Handle SIGTERM in the worker: finish the current job or put it back in the queue within stop_grace_period. A Telegram bot using long polling loses nothing in a short restart, because Telegram keeps pending updates for up to 24 hours.
Heartbeat: have the agent ping an uptime service every few minutes and alert when pings stop; this catches stuck loops.
Resources:
docker statsfor memory per container,df -handdocker system dffor disk.Cost: send yourself yesterday's token spend every morning from the
llm_usagetable.
How big a server does an AI agent need?
Agents calling external LLM APIs: mostly waiting on the network. As a rule of thumb, 1 vCPU and 2 GB of RAM run one or several bots plus PostgreSQL at modest traffic.
Headless browsers for scraping: each Chromium instance can use several hundred MB. Start at 4 GB and limit concurrency.
RAG with pgvector: the vector index wants to sit in RAM; 4 to 8 GB covers hundreds of thousands of chunks.
Local models: a 7B to 8B model quantized to 4-bit needs roughly 5 to 6 GB of RAM for weights alone and generates slowly on CPU. IM HOST plans are CPU servers without GPUs, so keep generation on an API and run only small local models such as embedding or classification models.
IM HOST AI Agent Hosting delivers an always-on server with Docker and Docker Compose, Node.js, Python and PostgreSQL with pgvector already installed, plus full root, free daily backups and a 1 Gbit port. Plans run from Agent Starter (1 vCPU, 2 GB) to Agent Scale (12 vCPU, 32 GB), in Germany, Poland, the UK, the USA or Egypt subject to stock. LLM API credits are not included: you bring your own keys or models. For broader AI apps, see our AI application hosting page.
Frequently asked questions
Do I need a GPU to run an AI agent?
Not if the agent calls a hosted LLM API, which is how most agents work. The heavy computation happens at the provider; your server runs the logic, tools and database. A GPU only matters if you want to run large models yourself.
Will my agent start again after the server reboots?
Yes, if the container has restart: unless-stopped (or always) and the Docker service is enabled with systemctl enable docker. A container you stopped manually with unless-stopped stays stopped.
How much does it cost to run an AI agent 24/7?
Two parts: a fixed monthly server cost, and a variable API cost per token. For a busy agent the tokens usually cost more than the server, which is why the budget table and step limits above matter more than squeezing the server price.
If you want a server that arrives with Docker, Python, Node.js and pgvector ready, look at AI Agent Hosting plans, starting from $8.99/mo. You get full root, daily backups and engineers on call 24/7 if a deployment step fails.
More from our blog
Discover more practical guides and product insights from the IM Host team.
View all articles