RAG App Hosting with pgvector on a Hostperl VPS

Start with the right VPS and a clean deployment plan
RAG app hosting works best when your app, vector store, and reverse proxy live on infrastructure you control. For support bots, sales assistants, and internal workflow agents, that usually means a VPS with enough RAM for Postgres, Redis, and your Node.js or Python app, plus a deployment path you can actually support when something breaks.
If you are deciding between serverless and a server you manage, our Vercel alternatives guide for private AI apps explains the trade-offs in plain language. For customers who want a right-sized machine and room to grow, a Hostperl VPS is usually the practical starting point.
In this tutorial, you will deploy a small RAG stack with Docker Compose, Postgres with pgvector, Redis, an app container, Nginx, and TLS. The example is written for 2026 and keeps data residency, APAC latency, secret handling, backups, and rollback in view.
What you will build
- A private RAG app that accepts documents and answers questions with retrieval
- Postgres with pgvector for embeddings and metadata
- Redis for cache and background work
- Nginx with HTTPS in front of the app
- Environment-based secrets and a rollback-friendly Compose layout
Use these placeholders as you read: SERVER_IP is your VPS address, for example 203.0.113.10. DOMAIN_NAME is the hostname you point at the server, for example rag.example.co.nz. ADMIN_USER is the non-root sudo user you will create, such as aiops. APP_DIR is the deployment directory, such as /opt/rag-app.
Log in and identify the operating system
On your local computer, connect as root first. Keep this session open until your new admin account works.
ssh root@203.0.113.10If your Hostperl VPS image uses a default non-root account, use that instead. Once connected, check the OS before you install anything.
cat /etc/os-releaseYou should see either Ubuntu/Debian or AlmaLinux/Rocky Linux. The rest of the steps split where the package manager and firewall tools differ.
Create a non-root admin user
Run this section on the VPS as root. A separate admin user lowers the risk of lockouts and keeps the deployment easier to support.
Ubuntu and Debian
adduser ADMIN_USER
usermod -aG sudo ADMIN_USERReplace ADMIN_USER with your account name. The first command creates the user and home directory. The second gives sudo access through the sudo group.
AlmaLinux and Rocky Linux
useradd -m ADMIN_USER
passwd ADMIN_USER
usermod -aG wheel ADMIN_USERHere, useradd -m creates the home directory, passwd sets the password, and wheel grants sudo rights.
Copy your SSH key and test the new login
Still on the VPS as root, create the SSH directory and set permissions. Then paste your public key into authorized_keys.
mkdir -p /home/ADMIN_USER/.ssh
nano /home/ADMIN_USER/.ssh/authorized_keys
chown -R ADMIN_USER:ADMIN_USER /home/ADMIN_USER/.ssh
chmod 700 /home/ADMIN_USER/.ssh
chmod 600 /home/ADMIN_USER/.ssh/authorized_keysReplace ADMIN_USER, paste one public key per line, then save and exit Nano with Ctrl+O, Enter, and Ctrl+X. Open a second terminal on your local computer and test the new account before you change any SSH settings.
ssh ADMIN_USER@203.0.113.10
sudo -vIf that works, keep the original root session open. Only then should you consider disabling password login or root SSH later, after your app is live and you have confirmed a recovery path.
Install Docker, Compose, and the base tools
This stack uses Docker Compose because it keeps the RAG app, Postgres, and Redis in one versioned deployment. That matters for migrations, support handoffs, and rollback.
On the VPS as the non-root sudo user, update packages first.
Ubuntu and Debian
sudo apt update
sudo apt install -y ca-certificates curl gnupg lsb-releaseAlmaLinux and Rocky Linux
sudo dnf -y install ca-certificates curl gnupg2 lsb-releaseInstall Docker and Compose using your distro’s supported method. If you already run Docker on the VPS, skip the install and confirm the version.
docker --version
docker compose versionYou should see a recent Docker Engine and Compose plugin. If those commands fail, fix Docker before continuing.
Prepare the app directory and environment file
Create a clean deployment directory. Keeping app files out of your home folder makes backups and restores simpler.
sudo mkdir -p /opt/rag-app
sudo chown -R ADMIN_USER:ADMIN_USER /opt/rag-app
cd /opt/rag-app
pwdNow create the environment file. This is where your secrets, model endpoint, and public URL live.
nano /opt/rag-app/.envPaste this example content, then replace the placeholders with your real values.
POSTGRES_DB=ragapp
POSTGRES_USER=ragapp
POSTGRES_PASSWORD=replace-with-a-long-random-password
DATABASE_URL=postgresql://ragapp:replace-with-a-long-random-password@db:5432/ragapp
REDIS_URL=redis://redis:6379/0
APP_PORT=3000
DOMAIN_NAME=rag.example.co.nz
NODE_ENV=production
OPENAI_API_KEY=replace-with-your-key
EMBEDDING_MODEL=text-embedding-3-smallSave and exit. Restrict the file so only the owner can read it.
chmod 600 /opt/rag-app/.env
ls -l /opt/rag-app/.envThe permissions should show -rw-------. That is the expected state.
Build the Compose stack
Create the Compose file next. It defines Postgres with pgvector, Redis, and the app service. For a smaller first deployment, this is enough to support a support bot or internal RAG assistant without adding extra moving parts.
nano /opt/rag-app/docker-compose.ymlPaste the full file below.
services:
db:
image: pgvector/pgvector:pg16
environment:
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- db_data:/var/lib/postgresql/data
restart: unless-stopped
redis:
image: redis:7.4-alpine
command: ["redis-server", "--appendonly", "yes"]
volumes:
- redis_data:/data
restart: unless-stopped
app:
image: ghcr.io/example/rag-app:2026
env_file: .env
depends_on:
- db
- redis
ports:
- "127.0.0.1:3000:3000"
restart: unless-stopped
volumes:
db_data:
redis_data:Save and exit. Before you start the stack, confirm the file syntax and the resolved config.
cd /opt/rag-app
docker compose configIf Docker prints the merged configuration without errors, the file is ready.
Start the containers and check the services
Bring the stack up in the background. The first run may take a minute while images download.
cd /opt/rag-app
docker compose up -dCheck status and the exposed local port.
docker compose ps
ss -tulpn | grep 3000You want to see the app container running and listening on 127.0.0.1:3000. If the app exits, inspect the logs immediately.
docker compose logs --tail=100 appFor teams comparing deployment models, our VPS and private deployment buyer guide explains why local control helps with data residency and support response times. If your workflow includes a Next.js front end, this pairs well with our Next.js VPS tutorial.
Install Nginx and publish the app safely
Now place Nginx in front of the container so your app can answer on port 80 and 443. This also gives you a stable place for TLS, rate limits, and future redirects.
Ubuntu and Debian
sudo apt install -y nginx
sudo ufw allow 'Nginx Full'
sudo ufw statusAlmaLinux and Rocky Linux
sudo dnf -y install nginx
sudo systemctl enable --now firewalld
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload
sudo firewall-cmd --list-allCreate the Nginx server block or virtual host file.
sudo nano /etc/nginx/sites-available/rag-appUbuntu and Debian file content
server {
listen 80;
server_name DOMAIN_NAME;
location / {
proxy_pass http://127.0.0.1:3000;
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 syntax.
sudo ln -s /etc/nginx/sites-available/rag-app /etc/nginx/sites-enabled/rag-app
sudo nginx -t
sudo systemctl reload nginxAlmaLinux and Rocky Linux file content
server {
listen 80;
server_name DOMAIN_NAME;
location / {
proxy_pass http://127.0.0.1:3000;
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;
}
}On Red Hat family systems, save the file in /etc/nginx/conf.d/rag-app.conf, then test and reload.
sudo nginx -t
sudo systemctl enable --now nginx
sudo systemctl reload nginxIssue TLS and confirm the public endpoint
Point DOMAIN_NAME at your VPS before you request the certificate. Then install Certbot if it is not already present.
Ubuntu and Debian
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d DOMAIN_NAMEAlmaLinux and Rocky Linux
sudo dnf -y install certbot python3-certbot-nginx
sudo certbot --nginx -d DOMAIN_NAMECertbot should add the HTTPS server block and reload Nginx automatically. Confirm renewal is scheduled.
sudo systemctl status certbot.timer || sudo systemctl list-timers | grep certbotNow test from the server and from your local computer.
curl -I http://DOMAIN_NAME
curl -I https://DOMAIN_NAMEYou should see a redirect to HTTPS and a successful response. If you get a 502, check the app logs and the container state again.
Add basic operational controls for AI apps
RAG systems do not fail like normal brochure sites. They fail when token use spikes, embeddings grow too fast, or a webhook sends garbage. Put a few controls in place now.
- Keep model keys in
.env, not in code. - Use Redis for short-lived cache entries and rate counters.
- Limit who can upload documents or trigger workflows.
- Back up
/opt/rag-app/.envand the Postgres volume on a schedule. - Store logs long enough to audit bad prompts and failed retrievals.
If your use case is a private support bot, review our private support bot walkthrough and the private RAG and bots guide for a tighter security posture.
Functional smoke test and rollback check
Open the site in a browser and submit one real question. Then confirm the app answered using retrieved content rather than only the model’s memory. If your app exposes a health endpoint, test that too.
curl -fsS https://DOMAIN_NAME/health
curl -fsS https://DOMAIN_NAME/api/ask -H 'Content-Type: application/json' -d '{"question":"What is our refund policy?"}'Replace the path and JSON body with your app’s actual routes. A successful response confirms the reverse proxy, container, and retrieval pipeline all work together.
For rollback, keep the previous image tag in your Compose file. If a deployment breaks, revert the tag, then run:
cd /opt/rag-app
docker compose up -d
sudo systemctl reload nginxThat returns you to the last known good app version without changing DNS or SSL.
Troubleshooting the most likely failures
502 Bad Gateway
docker compose ps
docker compose logs --tail=100 appIf the app is exited, fix the environment file or image tag, then restart the stack. If it runs locally but still fails through Nginx, confirm the proxy target is 127.0.0.1:3000.
Postgres cannot start
docker compose logs --tail=100 dbLook for permission or password errors. If the volume is new, check your POSTGRES_PASSWORD in .env. If you changed it after first boot, recreate the database volume only if you are sure the data is disposable.
TLS issuance fails
sudo nginx -t
sudo journalctl -u certbot --no-pager -n 100The usual clue is a DNS mistake or firewall block. Fix the A record, confirm port 80 is open, then run Certbot again.
Why this setup fits Hostperl customers
For APAC teams, a VPS close to your users lowers chat latency and keeps private data under your control. That matters for customer-service automations, internal knowledge bases, and sales assistants that should not depend on a public serverless layer.
If you want more room for Qdrant, a larger Postgres cache, or a separate worker box for embeddings, Hostperl can grow with you. Start with the smallest plan that handles your current document volume, then move up only when logs and CPU tell you to.
Hostperl is a strong fit if you want RAG app hosting with real support, clear accountability, and room for private workloads. If you are comparing deployment options, a Hostperl VPS gives you the control this stack needs, while our private AI hosting guide helps you plan spend and latency before launch.
FAQ
Can I swap pgvector for Qdrant or Chroma?
Yes. Keep the app container and Nginx layout the same, then change the vector backend and environment variables. For many small teams, pgvector is simpler to back up because it lives inside Postgres.
Should I run embeddings and webhooks on the same VPS?
You can for a small launch. If queues begin to lag or token use rises, split the worker into a second VPS and keep the database private.
How do I control AI hosting costs?
Use Redis caching, cap prompt size, log token usage per request, and right-size the VPS. Most teams spend more on oversized infrastructure than on the model itself during the first month.
Is this suitable for customer data?
Yes, if you keep secrets out of code, restrict access, and back up the database regularly. For regulated or regional workloads, Hostperl’s VPS-based approach gives you a cleaner data-residency story than many public serverless platforms.
What if I want a UI builder like Coolify or Dokploy later?
You can move to an open-source PaaS workflow later without changing the basic architecture. Start with Docker Compose, then add a panel only if you need team deployment workflows or easier app updates.
