Host an AI Support Bot Stack on VPS in 2026

What you are building
If you want to host an AI support bot stack on VPS, the simplest reliable setup is a small Node.js or FastAPI app, a vector store, a background worker, and a reverse proxy. That gives you control over data residency, APAC latency, and monthly spend. It also keeps the bot close to the systems it needs, which helps when customers expect quick answers and audit-friendly logs.
This tutorial uses Docker Compose so you can move the stack later without rewriting the deployment. It fits the kind of workloads Hostperl sees often on Hostperl VPS hosting: support bots, sales assistants, RAG apps, webhook processors, and private internal automations.
You will end with Nginx, HTTPS, environment files, persistent storage, and a simple smoke test. If you want more context on deployment choices, this AI app hosting decision guide and this APAC-focused private hosting guide are useful companions.
Decide the stack before you log in
Use these placeholders throughout the tutorial:
- SERVER_IP = your VPS IP, for example
203.0.113.10 - DOMAIN_NAME = your app domain, for example
bot.example.com - APP_USER = the non-root Linux account, for example
aiops - APP_DIR = the deployment folder, for example
/opt/ai-support-bot - APP_PORT = the internal app port, for example
3000 - POSTGRES_DB = the database name, for example
aidb - POSTGRES_USER = the database user, for example
aiuser
The example stack below uses three services: the app, PostgreSQL with pgvector, and a Redis queue for background jobs. That keeps retrieval fast and gives you a place to cache repeated answers or token-heavy prompts.
1) Connect to the VPS and check the OS
On your local computer, open SSH to the VPS. If your provider gives you a root login, use that first. If you already have a non-root admin account, use it instead.
ssh root@203.0.113.10If your server uses a default admin user, the command will look like this:
ssh admin@203.0.113.10After login, confirm the operating system before you install anything:
cat /etc/os-releaseYou should see ID=ubuntu, ID=debian, ID=almalinux, or ID=rocky. Keep the root session open for now. If you need help choosing the right plan for this kind of private workload, Hostperl’s VPS buyer guide explains the tradeoffs in plain language.
2) Create a non-root admin account
Run the commands for your distribution. Do not close the root session until the new login works in a second terminal.
Ubuntu and Debian
On the VPS as root, create the user, add sudo access, and prepare SSH keys.
adduser aiopsSet a password when prompted. Then grant sudo rights:
usermod -aG sudo aiopsCreate the SSH directory and lock it down:
mkdir -p /home/aiops/.ssh
chmod 700 /home/aiops/.ssh
cp /root/.ssh/authorized_keys /home/aiops/.ssh/authorized_keys
chmod 600 /home/aiops/.ssh/authorized_keys
chown -R aiops:aiops /home/aiops/.sshNow open a second terminal on your local computer and test the new login:
ssh aiops@203.0.113.10Then confirm sudo works:
sudo -v
whoamiYou should see aiops from whoami.
AlmaLinux and Rocky Linux
On the VPS as root, create the user and add wheel access.
useradd -m aiops
passwd aiops
usermod -aG wheel aiopsPrepare the SSH directory and permissions:
mkdir -p /home/aiops/.ssh
chmod 700 /home/aiops/.ssh
cp /root/.ssh/authorized_keys /home/aiops/.ssh/authorized_keys
chmod 600 /home/aiops/.ssh/authorized_keys
chown -R aiops:aiops /home/aiops/.sshTest the new account from a second terminal:
ssh aiops@203.0.113.10
sudo -v
whoamiOnly after the new login works should you consider disabling root password login later. That lowers the chance of locking yourself out.
3) Install Docker, Compose, Nginx, and the database tools
On the VPS as the non-root sudo user, install the runtime. The commands differ by distribution.
Ubuntu and Debian
sudo apt update
sudo apt install -y ca-certificates curl gnupg lsb-release ufw nginx docker.io docker-compose-plugin postgresql-client redis-toolsCheck the versions so you know what you are running:
docker --version
docker compose version
nginx -vAlmaLinux and Rocky Linux
sudo dnf -y install epel-release
sudo dnf -y install curl ca-certificates nginx docker docker-compose-plugin postgresql redis
sudo systemctl enable --now docker nginxCheck versions:
docker --version
docker compose version
nginx -vIf you are comparing app-platform choices for a client or agency, this Vercel alternatives post and the APAC hosting guide show why VPS-based deployments often fit private bot workloads better than serverless-only setups.
4) Create the application directory and Compose file
On the VPS as the non-root sudo user, create the project folder and switch into it.
sudo mkdir -p /opt/ai-support-bot
sudo chown -R aiops:aiops /opt/ai-support-bot
cd /opt/ai-support-bot
pwdCreate the environment file. Replace the placeholders with your real secrets and domain values.
nano .envPaste this content, then save and exit with Ctrl+O, Enter, and Ctrl+X.
DOMAIN_NAME=bot.example.com
APP_PORT=3000
APP_USER=aiuser
POSTGRES_DB=aidb
POSTGRES_USER=aiuser
POSTGRES_PASSWORD=change-this-long-password
REDIS_URL=redis://redis:6379
DATABASE_URL=postgresql://aiuser:change-this-long-password@postgres:5432/aidb
OPENAI_API_KEY=replace-with-your-key
ANTHROPIC_API_KEY=replace-with-your-key
APP_ENV=productionRestrict file access immediately:
chmod 600 .envNow create the Compose file.
nano compose.ymlPaste the following and save it the same way. This app example assumes your support bot exposes a web UI and a /health endpoint.
services:
postgres:
image: pgvector/pgvector:pg16
container_name: ai-postgres
restart: unless-stopped
environment:
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
interval: 10s
timeout: 5s
retries: 5
redis:
image: redis:7.4-alpine
container_name: ai-redis
restart: unless-stopped
app:
build: .
container_name: ai-app
restart: unless-stopped
env_file: .env
ports:
- "127.0.0.1:${APP_PORT}:3000"
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_started
volumes:
- ./data:/app/data
command: ["node", "server.js"]
volumes:
pgdata:Before you start containers, create the application files.
5) Add a minimal app, worker, and health check
On the VPS as the non-root sudo user, create a basic Node.js app. This version serves a support form, writes jobs to Redis, and exposes a health endpoint. Replace it later with your real bot logic.
nano package.jsonPaste this file content and save it:
{
"name": "ai-support-bot",
"version": "1.0.0",
"type": "module",
"scripts": {
"start": "node server.js",
"worker": "node worker.js"
},
"dependencies": {
"express": "^4.21.2",
"ioredis": "^5.6.1",
"pg": "^8.13.1"
}
}Create the server:
nano server.jsimport express from 'express';
import Redis from 'ioredis';
const app = express();
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
const redis = new Redis(process.env.REDIS_URL);
const port = process.env.APP_PORT || 3000;
app.get('/health', async (_req, res) => {
try {
await redis.ping();
res.json({ ok: true, service: 'ai-support-bot' });
} catch (error) {
res.status(503).json({ ok: false, error: 'redis-unavailable' });
}
});
app.get('/', (_req, res) => {
res.send('AI Support Bot
');
});
app.post('/submit', async (req, res) => {
const message = (req.body.message || '').trim();
if (!message) return res.status(400).send('Message is required');
await redis.lpush('support_jobs', JSON.stringify({ message, createdAt: new Date().toISOString() }));
res.send('Queued for processing');
});
app.listen(port, '0.0.0.0', () => console.log(`Listening on ${port}`));Create the worker:
nano worker.jsimport Redis from 'ioredis';
const redis = new Redis(process.env.REDIS_URL);
async function loop() {
while (true) {
const item = await redis.brpop('support_jobs', 0);
console.log('Processing job:', item[1]);
}
}
loop().catch(err => {
console.error(err);
process.exit(1);
});Create a simple Dockerfile:
nano DockerfileFROM node:22-bookworm-slim
WORKDIR /app
COPY package.json ./
RUN npm install --omit=dev
COPY server.js worker.js ./
CMD ["node", "server.js"]Run a local syntax and build check before you start the stack.
docker compose config
docker compose buildIf the config prints merged YAML and the build finishes, the files are ready.
6) Start the stack and confirm the services
On the VPS as the non-root sudo user, start the containers and let Docker’s restart policy bring them back after a reboot.
docker compose up -dCheck the status of each container:
docker compose psLook for Up or healthy states. Then inspect the app logs:
docker compose logs --tail=50 app
docker compose logs --tail=50 postgres
docker compose logs --tail=50 redisIf you also want a separate retrieval database, Hostperl’s pgvector RAG tutorial goes deeper into schema design and ingest flow.
7) Add Nginx as the public reverse proxy
On the VPS as the non-root sudo user, create an Nginx site that forwards traffic to the local app port. This keeps your app private on 127.0.0.1 and lets Nginx handle TLS.
First create the site file.
sudo nano /etc/nginx/sites-available/ai-support-botUse this content on Ubuntu and Debian:
server {
listen 80;
server_name bot.example.com;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
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;
}
}Enable it and test the syntax:
sudo ln -s /etc/nginx/sites-available/ai-support-bot /etc/nginx/sites-enabled/ai-support-bot
sudo nginx -tOn AlmaLinux and Rocky Linux, use /etc/nginx/conf.d/ai-support-bot.conf instead, with the same server block. Then run:
sudo nginx -tIf syntax passes, reload Nginx:
sudo systemctl reload nginxOpen the firewall before you remove any old access rules.
8) Open the firewall safely
On the VPS as the non-root sudo user, allow SSH, HTTP, and HTTPS.
Ubuntu and Debian
sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw enable
sudo ufw status verboseAlmaLinux and Rocky Linux
sudo systemctl enable --now firewalld
sudo firewall-cmd --permanent --add-service=ssh
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload
sudo firewall-cmd --list-allIf SELinux is enforcing, keep Nginx on the standard HTTP port and avoid custom public ports. That usually avoids extra policy work.
9) Issue TLS and test the public site
Point DOMAIN_NAME to your VPS first. Then install Certbot.
Ubuntu and Debian
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d bot.example.comAlmaLinux and Rocky Linux
sudo dnf -y install certbot python3-certbot-nginx
sudo certbot --nginx -d bot.example.comChoose the redirect option when Certbot asks. Then confirm renewal is scheduled:
sudo certbot renew --dry-runFrom your local computer, check HTTPS and the health endpoint:
curl -I https://bot.example.com
curl https://bot.example.com/healthYou should see a successful TLS response and JSON showing ok: true.
10) Add practical controls for cost, security, and rollback
AI support bots can spend money quietly. Put guardrails in the app before you hand it to customers or agents.
- Set request rate limits in the app or Nginx if you expose public chat.
- Cache repeated retrieval results for common questions.
- Keep API keys in
.envor a secrets manager, never in code. - Log prompt, model, token count, and latency for each job.
- Store database backups off the VPS.
For a concrete backup habit, create a database dump and test restore on a schedule. Hostperl’s backup testing guide is a good reference if this bot matters to operations or revenue.
If you want an open-source control panel later, Hostperl supports private workloads well on Hostperl VPS hosting, including Docker Compose stacks that need predictable CPU, memory, and regional latency.
If you are deploying a customer-facing bot, a right-sized VPS is usually the simplest way to keep control over data, logs, and cost. Hostperl gives you room to run private AI apps, RAG services, and queue workers without forcing a serverless setup that does not fit your workflow. Start with Hostperl VPS for the app layer and keep your bot close to your users in APAC.
Final checks from the server and your laptop
On the VPS as the non-root sudo user, confirm the services survived startup and are listening where expected:
docker compose ps
sudo ss -tulpn | grep -E ':(80|443|3000)'
sudo systemctl status nginx --no-pagerOn your local computer, submit a real test message and watch the queue:
curl -X POST https://bot.example.com/submit -d 'message=How do I reset my password?'
ssh aiops@203.0.113.10 "cd /opt/ai-support-bot && docker compose logs --tail=20 app"You should see the request accepted and a job queued in the logs. Reboot the server once before you hand it over to production so you know the stack starts cleanly.
sudo rebootAfter the VPS comes back, reconnect and run:
docker compose ps
curl -s https://bot.example.com/healthIf you are migrating from a hosted platform like Vercel, Render, or Railway, this is usually where VPS hosting starts paying off: clearer cost control, direct logs, and fewer surprises around data residency. For support bots, that matters more than marketing labels. Hostperl’s managed hosting team can also help you size the first VPS so you do not overbuy on day one.
Troubleshooting the most likely failures
Nginx returns 502
docker compose logs --tail=50 app
sudo ss -tulpn | grep 3000If the app is not listening, check the container command and restart the stack:
docker compose restart appHTTPS fails after Certbot
sudo nginx -t
sudo certbot certificatesIf the config is invalid, fix the site file and reload Nginx only after nginx -t passes.
Redis jobs never process
docker compose logs --tail=50 redis
docker compose exec app node -e "import Redis from 'ioredis'; const r=new Redis(process.env.REDIS_URL); const x=await r.lrange('support_jobs',0,-1); console.log(x); process.exit(0)"If the queue has items but nothing processes them, run the worker separately or add it to Compose as a second app service.
FAQ
Can I run pgvector and my app on the same VPS?
Yes. For small and medium support bots, one VPS is often enough. Keep an eye on memory use, and move the database to a larger plan only when the queue, embeddings, or concurrent chat load grows.
Should I use Vercel for this kind of bot?
Use Vercel for static front ends or light Next.js pages. For private AI bots, queues, and database-heavy retrieval, a VPS usually gives you better control over jobs, logs, and costs.
How do I keep API keys out of logs?
Store them in .env, set file mode to 600, and never print the environment file in chat logs or deployment hooks.
What should I back up first?
Back up PostgreSQL, then the .env file, then your Compose and Nginx configs. Those three items are enough to rebuild most bot stacks quickly.
Is this setup suitable for APAC teams?
Yes. Keeping the bot on a regional VPS usually cuts latency and simplifies data-residency conversations with customers and internal stakeholders.
