How to Build a Self-Hosted AI Tools Stack on a Single VPS
This article contains affiliate links. If you sign up for a service through one of these links, we may receive a commission at no additional cost to you. We only recommend tools we would use ourselves — see our editorial policy.
A single VPS is often enough to run a useful self-hosted AI content stack. The common mistake is overbuilding before understanding what the system actually needs to do.
A practical single-VPS setup for content and automation work usually needs four things: a stable operating system, a way to isolate services, a way to expose the public site, and a clear separation between draft and live content. The deployment checklist gives you a step-by-step validation sequence for exactly this kind of stack.
Ubuntu LTS covers the first requirement reliably. It has broad documentation, long support cycles, and a mature ecosystem. If the machine runs around the clock, stability beats novelty.
Docker handles service isolation. It reduces the chance that packages, libraries, and runtime environments drift into an undocumented mess. If your stack includes a static site generator, automation scripts, a reverse proxy, and possibly a web interface, containers let you update or restart any part without pulling everything down.
Caddy works well as a reverse proxy on a budget VPS. It manages TLS automatically and requires minimal configuration for a simple site. What you expose should be minimal: the generated site, and perhaps an admin interface behind basic authentication.
The application layer is where most people overcomplicate things. For a content-focused workflow, you need a drafts directory, a validation step, a build command, and a deploy script. A lightweight automation layer helps if you want to schedule tasks or connect research to publishing. Keep services that are not public off the public network.
The strongest reason to start on one VPS is not cost alone. It is clarity. When something breaks, you know where to look. When logs matter, they are nearby. When a page renders badly, the path from markdown to HTML is short enough to trace.
Self-hosting works best when the stack is legible, not when it looks impressive. A system you can rebuild from notes in two hours is worth more than a system you cannot explain.
A sensible way to plan the stack is to define what absolutely must run on day one and what can wait. For many content or SEO workflows, the day-one stack is smaller than people think. You need a public site, a place for drafts, a build process, backups, and perhaps one automation component. You do not need a vector database, local GPU inference, message queues, and five dashboards just because those tools exist.
A practical single-VPS layout might include:
- Ubuntu LTS as the host OS
- Docker Compose for service definitions
- Caddy for TLS and public routing
- a static site generator container for builds
- a small automation service or cron jobs for routine tasks
- persistent volumes for content, generated output, and logs
With that layout, the site can publish reliably while remaining easy to reason about. If an article fails to build, the issue is usually in the content or build container. If the site is down publicly, the issue is usually Caddy or the host. Simplicity narrows the search space.
You should also define what stays private. Internal dashboards, draft tools, and admin interfaces do not need public exposure unless there is a specific reason. Keeping those services on an internal network reduces risk and makes the public surface area much smaller.
Setting it up
The commands below assume a fresh Ubuntu 24.04 LTS server, a non-root user with sudo, and a domain whose A record already points at the server's IP. That last part matters before you start, not after.
Install Docker Engine and the Compose plugin using the official Ubuntu package instructions. Then verify the installation. Adding a user to the Docker group grants root-equivalent control of the host; only do this for a trusted administrator:
sudo docker version
sudo usermod -aG docker $USER
# log out and back in for the group change to apply
Create one directory that holds the whole stack. Everything in it except the generated output belongs in git:
/srv/stack/
├── docker-compose.yml
├── Caddyfile
├── site/ # markdown source and SSG config
└── public/ # generated HTML that Caddy serves
A minimal docker-compose.yml for this layout looks like this (an Eleventy site is used as the example; swap the build command for Hugo or whatever you run):
services:
caddy:
image: caddy:2
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- ./public:/srv/public:ro
- caddy_data:/data
builder:
image: node:22-slim
working_dir: /site
volumes:
- ./site:/site
- ./public:/site/_site
command: sh -c "npm ci && npx @11ty/eleventy --output=/site/_site"
profiles: ["build"]
volumes:
caddy_data:
The profiles: ["build"] line keeps the builder from starting with the stack; it only runs when you invoke it. The caddy_data volume stores TLS certificates, so they survive container recreation.
The Caddyfile is three lines plus your domain:
example.com {
root * /srv/public
file_server
encode gzip
}
First run:
cd /srv/stack
docker compose up -d caddy
docker compose run --rm builder
docker compose logs caddy
On that first start, Caddy requests a certificate from Let's Encrypt automatically. If the logs show a successful certificate obtain and your site loads over HTTPS, the public half is done. If certificate issuance fails, the cause is almost always DNS not yet pointing at the server or port 80 blocked.
For scheduled rebuilds, a host cron entry is enough:
0 * * * * cd /srv/stack && docker compose run --rm builder >> /var/log/site-build.log 2>&1
One gotcha worth knowing before you rely on a firewall: Docker publishes container ports through its own iptables rules, which bypass UFW. If you add a service with ports: - "5678:5678", that port is reachable from the internet even if ufw status says otherwise. For anything internal, bind to localhost instead, for example "127.0.0.1:5678:5678", and reach it over an SSH tunnel or put it behind Caddy with basic auth. This applies to automation tools like n8n, admin panels, and databases alike.
For the VPS itself, Hetzner's CX22 (2 vCPU / 4 GB RAM) or CX32 (4 vCPU / 8 GB RAM) is a cost-effective starting point for a content workflow stack in Europe. In North America, DigitalOcean's Basic Droplets provide a similar baseline with strong documentation. Both providers offer automated backups as an add-on, which is worth enabling from day one rather than retrofitting later.
Resource planning matters on a single VPS because everything competes for the same RAM and CPU. Lightweight automation and static site generation are usually fine on modest hardware. Local LLM inference is where costs rise quickly. Many small publishers therefore use self-hosted workflow infrastructure but call external model APIs rather than running large models locally. That hybrid approach is often the best use of a small VPS.
Finally, document the stack as if you will have to rebuild it after a failed update. Keep Compose files, environment examples, backup instructions, and deployment commands in version control. On a single machine, good notes are part of the architecture. They are what turn a one-off server into an operable system.
Readers working through adjacent decisions should also look at How to Choose a VPS for a Self-Hosted AI Stack and Webhook Automation for AI Workflows: How to Build Real-Time Systems That Hold Up.
Sources
This article is for general information only and should not be treated as professional advice.