Private AI App Hosting on VPS for RAG and Bots

Set up the server and choose the right runtime
Private AI app hosting on VPS works best when you keep the stack tight: one app server, one database or vector store, one reverse proxy, and clear secret handling. That is usually enough for support bots, sales bots, internal workflow agents, and RAG apps that need APAC-friendly latency and tighter data control.
If you want a managed place to start while you test traffic, Hostperl VPS hosting gives you room to run Node.js, FastAPI, Docker Compose, pgvector, or Qdrant without tying yourself to a serverless platform that may not fit audit or residency requirements.
Assumptions and placeholders: replace SERVER_IP with your VPS address, DOMAIN_NAME with the hostname you will point to the server, ADMIN_USER with your sudo user, APP_USER with the service account for the app, APP_DIR with the deployment directory, and APP_PORT with the internal app port. Example: SERVER_IP=203.0.113.10, DOMAIN_NAME=ai.example.com, ADMIN_USER=hostperl, APP_USER=aiapp, APP_DIR=/srv/ai-app, APP_PORT=3000.
On your local computer, connect to the VPS as root for the first login:
ssh root@203.0.113.10If your provider gives you a default non-root account, use that instead. Once you are in, confirm the operating system before you install anything:
cat /etc/os-releaseYou should see either Ubuntu/Debian or AlmaLinux/Rocky Linux. The commands below are split so you do not mix package managers or firewall tools.
Ubuntu and Debian
On the VPS as root, update the system and add a non-root admin account. Keep the root session open until the new login works.
apt update
apt -y upgrade
adduser ADMIN_USER
usermod -aG sudo ADMIN_USERReplace ADMIN_USER with your chosen account name. You should see the user added to the sudo group.
Install the tools you need for deployment, firewalling, and TLS:
apt -y install curl git ufw ca-certificates gnupg lsb-releaseCreate the SSH directory and copy your public key. Replace the key string with your actual id_ed25519.pub content.
install -d -m 700 -o ADMIN_USER -g ADMIN_USER /home/ADMIN_USER/.ssh
cat >> /home/ADMIN_USER/.ssh/authorized_keys <<'EOF'
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEXAMPLEKEYCHANGEIT user@example
EOF
chown ADMIN_USER:ADMIN_USER /home/ADMIN_USER/.ssh/authorized_keys
chmod 600 /home/ADMIN_USER/.ssh/authorized_keysNow open a second terminal and test the new account before you touch root login settings:
ssh ADMIN_USER@203.0.113.10
sudo -v
whoami
idSuccessful output should show ADMIN_USER and confirm sudo access. Only after that should you consider disabling password authentication or root login.
Open the firewall in a safe order. Allow SSH first so you do not lock yourself out, then enable UFW:
ufw allow OpenSSH
ufw allow 80/tcp
ufw allow 443/tcp
ufw --force enable
ufw status verboseYou should see SSH, HTTP, and HTTPS allowed.
AlmaLinux and Rocky Linux
On the VPS as root, update packages and create the admin account with wheel access:
dnf -y update
useradd -m ADMIN_USER
passwd ADMIN_USER
usermod -aG wheel ADMIN_USERInstall the base tools and firewall service:
dnf -y install curl git firewalld ca-certificates
systemctl enable --now firewalldSet up the SSH key with safe permissions:
install -d -m 700 -o ADMIN_USER -g ADMIN_USER /home/ADMIN_USER/.ssh
cat >> /home/ADMIN_USER/.ssh/authorized_keys <<'EOF'
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEXAMPLEKEYCHANGEIT user@example
EOF
chown ADMIN_USER:ADMIN_USER /home/ADMIN_USER/.ssh/authorized_keys
chmod 600 /home/ADMIN_USER/.ssh/authorized_keysIn a second terminal, verify that the new account can log in and use sudo:
ssh ADMIN_USER@203.0.113.10
sudo -v
whoami
idOpen the necessary ports before any SSH hardening:
firewall-cmd --permanent --add-service=ssh
firewall-cmd --permanent --add-service=http
firewall-cmd --permanent --add-service=https
firewall-cmd --reload
firewall-cmd --list-allInstall Docker and prepare the app directory
For AI bots and RAG apps, Docker Compose keeps the deployment portable. That matters if you later move away from a serverless platform such as Vercel or Railway after costs rise or webhooks get flaky. It also gives you a cleaner rollback path.
On the VPS as the non-root sudo user, create the app directory and install Docker. If you prefer a different runtime, you can still follow the reverse proxy and environment file pattern shown here.
Ubuntu and Debian
sudo mkdir -p /srv/ai-app
sudo chown -R ADMIN_USER:ADMIN_USER /srv/ai-app
curl -fsSL https://get.docker.com | sudo sh
sudo usermod -aG docker ADMIN_USER
docker --version
docker compose versionLog out and back in so the Docker group change applies. Your version output should print both Docker and Compose details.
AlmaLinux and Rocky Linux
sudo mkdir -p /srv/ai-app
sudo chown -R ADMIN_USER:ADMIN_USER /srv/ai-app
sudo dnf -y install dnf-plugins-core
curl -fsSL https://get.docker.com | sudo sh
sudo systemctl enable --now docker
sudo usermod -aG docker ADMIN_USER
docker --version
docker compose versionAgain, open a new session after group changes. Hostperl customers often run these stacks on a managed VPS hosting plan when they want more control than shared hosting and less overhead than a dedicated box.
Build the private AI app hosting on VPS stack
This example uses three containers: a Node.js app, PostgreSQL with pgvector, and Nginx on the host as a reverse proxy. The same shape works for a FastAPI app, a Next.js API route layer, or an agent worker that consumes webhooks and sends replies to Slack or email.
On the VPS as the non-root sudo user, create a Docker Compose file and a secret environment file.
cd /srv/ai-app
mkdir -p app nginxCreate .env with restrictive permissions:
cat > /srv/ai-app/.env <<'EOF'
APP_PORT=3000
DOMAIN_NAME=ai.example.com
POSTGRES_DB=aiapp
POSTGRES_USER=aiapp
POSTGRES_PASSWORD=replace-with-a-long-random-password
OPENAI_API_KEY=replace-with-your-provider-key
EMBEDDINGS_MODEL=text-embedding-3-small
EOF
chmod 600 /srv/ai-app/.envNow create the Compose file:
cat > /srv/ai-app/docker-compose.yml <<'EOF'
services:
app:
image: node:22-alpine
working_dir: /app
command: sh -c "npm install && node server.js"
env_file: .env
volumes:
- ./app:/app:ro
ports:
- "127.0.0.1:${APP_PORT}:3000"
depends_on:
- db
restart: unless-stopped
db:
image: pgvector/pgvector:pg16
environment:
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- pgdata:/var/lib/postgresql/data
restart: unless-stopped
volumes:
pgdata:
EOFCreate a small app that exposes a health check, a retrieval-ready status page, and a webhook endpoint. This example stays simple on purpose, so you can swap in OpenClaw, NestJS, Next.js, or FastAPI later.
cat > /srv/ai-app/app/package.json <<'EOF'
{
"name": "ai-app",
"version": "1.0.0",
"type": "module",
"dependencies": {"express": "^4.21.2"}
}
EOF
cat > /srv/ai-app/app/server.js <<'EOF'
import express from 'express';
const app = express();
app.use(express.json());
app.get('/health', (req, res) => res.json({ ok: true, service: 'ai-app' }));
app.post('/webhook', (req, res) => res.json({ received: true, at: new Date().toISOString() }));
app.get('/', (req, res) => res.send('Private AI app hosting on VPS is working'));
app.listen(3000, '0.0.0.0');
EOFBring the stack up and confirm the containers are running:
cd /srv/ai-app
docker compose up -d
docker compose ps
docker compose logs --tail=50You should see both services healthy enough to start, and the app should bind only to 127.0.0.1 on the host.
For teams comparing serverless and VPS pricing, this is where a right-sized server often wins. If your bot spends most of its time waiting on model calls, a small VPS keeps the fixed cost predictable and easier to explain to clients. That is one reason many agencies start with VPS hosting for AI apps and only scale out after they know their request volume.
Put Nginx in front and enable SSL
Nginx gives you a stable public endpoint, request logging, rate limiting, and a clean place to terminate TLS. It also keeps your app port off the internet, which helps with audit trails and rollback planning.
On the VPS as root or via sudo, install Nginx and Certbot.
Ubuntu and Debian
sudo apt -y install nginx certbot python3-certbot-nginxAlmaLinux and Rocky Linux
sudo dnf -y install nginx certbot python3-certbot-nginx
sudo systemctl enable --now nginxCreate the site configuration. Replace DOMAIN_NAME and APP_PORT as needed.
sudo tee /etc/nginx/sites-available/ai-app.conf >/dev/null <<'EOF'
server {
listen 80;
server_name DOMAIN_NAME;
location / {
proxy_pass http://127.0.0.1:APP_PORT;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
EOFUbuntu and Debian only, enable the site and test syntax before reload:
sudo ln -s /etc/nginx/sites-available/ai-app.conf /etc/nginx/sites-enabled/ai-app.conf
sudo nginx -t
sudo systemctl reload nginxAlmaLinux and Rocky Linux only, place the file under /etc/nginx/conf.d/ instead:
sudo cp /etc/nginx/sites-available/ai-app.conf /etc/nginx/conf.d/ai-app.conf
sudo nginx -t
sudo systemctl reload nginxRun Certbot after DNS for DOMAIN_NAME points to SERVER_IP:
sudo certbot --nginx -d DOMAIN_NAMEAfter issuance, test the renewal path:
sudo certbot renew --dry-runIf you are setting up a customer-facing bot, read Run a Private AI Support Bot on VPS in 2026 and Deploy a Private RAG Stack on VPS in 2026 for the next layer of app structure. Those guides fit well if you need retrieval pipelines, vector search, or a more complete bot runtime.
Check the app, logs, and reboot persistence
Before you hand this to a client or a support team, confirm that everything responds after a reboot and that the logs are readable. That matters more than model choice when the bot is answering customers at 09:00 Monday morning.
On the VPS as the non-root sudo user, run the server-side checks:
docker compose ps
curl -fsS http://127.0.0.1:3000/health
curl -fsS https://DOMAIN_NAME/health
sudo systemctl status nginx --no-pager
sudo journalctl -u nginx -n 50 --no-pagerYou should get JSON from the health endpoint and an active Nginx service. Then test the webhook path:
curl -fsS -X POST https://DOMAIN_NAME/webhook -H 'Content-Type: application/json' -d '{"event":"test"}'Expected output includes received:true. To confirm persistence, reboot the server and recheck the same endpoints:
sudo rebootAfter reconnection, rerun docker compose ps, curl -fsS https://DOMAIN_NAME/health, and sudo systemctl status nginx --no-pager. If they all pass, your deployment survived a full restart.
Keep RAG, vectors, and costs under control
Once the app is live, the next risk is cost drift. Token usage, oversized models, and uncached retrieval calls can double a bill before anyone notices. Put request counts and model calls into your application logs, and store daily summaries in a database table or a simple CSV export.
- Use smaller embedding models for ordinary support search.
- Cache repeated retrieval results for the same customer question.
- Keep the vector store on the same VPS or private network segment when residency matters.
- Use a separate API key for each environment so you can revoke staging without touching production.
For RAG-specific deployment patterns, the internal guide RAG App Hosting on VPS with pgvector and Docker Compose pairs well with this setup. It is especially useful if you are choosing between pgvector, Qdrant, and Chroma for a customer project.
If you are comparing platforms such as Render, Netlify, Railway, or Vercel, remember the operational difference. Serverless is convenient for bursts, but a VPS gives you fixed process control, local logs, and an easier story for secure environment variables, backups, and rollback. That is why many teams begin with a private stack and only use serverless for front-end delivery.
Hostperl is a practical fit when you need private AI app hosting on VPS and support that understands migrations, DNS, and real launch deadlines. If you want more room for Docker Compose, vector search, and background jobs, start with Hostperl VPS or move to a larger plan as usage grows.
For agencies and service teams, the cleaner path is usually one controlled server, one clear rollback plan, and one support contact that knows the stack.
FAQ
Can I run this stack with OpenClaw, Coolify, Dokploy, or CapRover?
Yes. The same app, database, and reverse proxy layout works well with open-source PaaS tools. Keep the secrets in environment files and test your backup and restore flow.
Should I use pgvector, Qdrant, or Chroma?
Use pgvector if you already rely on PostgreSQL and want fewer moving parts. Use Qdrant if vector search is the core workload. Use Chroma for small internal projects and prototypes.
What is the safest way to handle API keys?
Store them in a root-owned or service-owned .env file with chmod 600, then restart the service after rotation. Never commit secrets to Git.
How do I reduce prompt-injection risk?
Keep the retrieval set limited, validate tool calls, and block any agent action that can change billing, delete data, or send messages without approval.
When should I move off a VPS?
Move only when CPU, memory, or storage pressure becomes constant, or when your team needs a larger shared runtime than one server can safely provide.
For related setup work, Hostperl also publishes practical guidance on private support-bot hosting and OpenClaw hosting for AI agents and RAG apps. Those are useful next reads if you are turning this single-server deployment into a production workflow.
