Running Hermes Agent in Docker on a VPS with Dokploy
A practical guide to deploying Hermes Agent in Docker on a VPS using Dokploy. What works, what broke, and what I wish I had known on day one.
I am terrible at documenting things while I build them. I set up whole infrastructure, break it three times, fix it in ways I barely understand, then someone asks "how did you do that?" and I have no idea.
This post is me fixing that.
Hermes is an open-source AI agent platform by Nous Research. It connects to Telegram, has persistent memory, runs terminal commands, browses the web, manages cron jobs, and calls APIs. All in Docker on your own VPS.
Not a "what is Hermes" post. A "how do I actually get this running and keep it running" post.
The Stack
A VPS gives you uptime. Docker gives you reproducibility and isolation. Dokploy gives you a web UI for containers, reverse proxy with SSL, and health checks without SSH-ing in every time.
The Dockerfile
Start from the official base image:
FROM nousresearch/hermes-agent:latest
USER root
RUN apt-get update && apt-get install -y \
curl wget git unzip zip vim \
htop tmux jq build-essential \
python3 python3-pip python3-venv \
nodejs npm docker.io \
&& rm -rf /var/lib/apt/lists/*
The agent will curl, git clone, and pip install. Better in the image than reinstalling on every restart.
Add Python and Node packages for the agent's tooling:
RUN pip3 install --break-system-packages \
openai requests rich typer \
fastapi uvicorn \
google-api-python-client \
google-auth-httplib2 google-auth-oauthlib
RUN npm install -g pnpm yarn typescript ts-node
The docker-compose.yml
services:
hermes-agent:
build: { context: ., dockerfile: Dockerfile }
container_name: hermes-agent
restart: unless-stopped
cap_add: [SETUID, SETGID]
security_opt: [seccomp=unconfined]
stdin_open: true
tty: true
expose: ["8081", "9119"]
volumes:
- hermes_data:/home/hermes/.hermes
- hermes_workspace:/workspace
networks: [dokply-network]
volumes:
hermes_data:
hermes_workspace:
networks:
dokploy-network:
external: true
Three things that matter:
seccomp=unconfined is required for the browser tool. Chromium's sandbox needs system calls Docker blocks by default. Without it, the browser tool crashes in painful ways.
stdin_open and tty keep the container alive. Hermes Gateway is a long-lived process that needs an attached terminal.
Two volumes: hermes_data for config, memory, and sessions. hermes_workspace for all working files. Destroy the container, rebuild, everything comes back.
The Start Command
sh -c '
set -e
post-deploy
mkdir -p /home/hermes/.hermes
if [ ! -f /home/hermes/.hermes/config.yaml ]; then
hermes config set model.provider "openrouter"
hermes config set model.default "openrouter/owl-alpha"
hermes config set memory.provider holographic
fi
exec hermes gateway run
'
The if block only runs on first boot. exec replaces the shell so gateway becomes PID 1. Without exec, the container exits immediately.
Dokploy Setup
Create a new application in Dokploy. Point it at the folder with your compose file. Set your environment variables. Deploy.
Keep the gateway port internal. Only expose the Telegram webhook through Dokploy's reverse proxy.
Persistence
Store identity and config files in a private GitHub repo. A post-deploy script restores from there on every container start. The volumes keep the memory database and all files safe across rebuilds.
Destroy the container, rebuild from scratch, agent comes back with full memory intact.
What Broke
Chromium browser tool kept failing. Fix: seccomp=unconfined.
Config was lost on restart. Fix: check for existing config before writing new.
Container exited immediately. Fix: exec hermes gateway run so the gateway is PID 1.
The Takeaway
Self-hosting an AI agent sounds intimidating. It is not. A base image, a Dockerfile with tools, a compose file that persists data, and a deployment platform. That is the whole stack.
The real value is what the agent does once it runs. Background tasks you never think to automate. A system that remembers what you worked on three weeks ago because it was there for all of it.
Set it up once. Let it run. Forget about it until it messages you.

Whilmar Bitoco
Full-Stack Developer & Aspiring Cloud Engineer