Deploying a side project on a single cheap VPS reliably
Deploying a side project on a single cheap VPS is the best way to master infrastructure. Learn to secure, automate, and manage your stack without breaking.

Deploying a side project on a single cheap VPS is a rite of passage for every developer who wants to move beyond the "it works on my machine" phase. When you're paying $5 a month for a box with 1GB of RAM, you learn to appreciate efficiency and hate bloat.
I’ve spent years moving apps from expensive managed platforms back to bare-metal VPS instances. It’s not just about the money; it’s about understanding exactly what happens when a request hits your server.
The "Too Cheap" Trap
My first mistake was trying to run a full microservices architecture on a tiny droplet. I had a database, a cache, and three separate containers for my API. Within two hours, the OOM (Out of Memory) killer was working overtime. The system spent more time swapping to disk than executing code.
I learned the hard way that when you're working with limited resources, simplicity is your best feature. Forget Kubernetes. Forget complex service meshes. If you're building a side project, stick to a single-server setup using Docker and a reverse proxy. It’s fast, portable, and keeps your memory footprint predictable.
Setting Up Your Environment
For a standard stack (Node.js, Python, or Go with a PostgreSQL backend), I usually stick to Ubuntu 22.04 LTS. It’s boring, stable, and has the best community support when things go sideways.
Before you even git clone your repo, harden the box:
- Create a non-root user: Never run as root. Ever.
- Setup UFW: Only open ports 22 (SSH), 80 (HTTP), and 443 (HTTPS).
- Swap space: Even with 1GB of RAM, adding 2GB of swap prevents the kernel from panicking during a
npm installor a heavy database migration.
Why Docker is Essential for Side Projects
Using Docker on a single cheap VPS makes your deployments atomic. Instead of manually installing dependencies, you define your environment in a Dockerfile.
Dockerfile# A simple multi-stage build to keep the image small FROM node:20-alpine AS builder WORKDIR /app COPY package*.json ./ RUN npm install COPY . . RUN npm run build FROM node:20-alpine WORKDIR /app COPY --from=builder /app/dist ./dist COPY --from=builder /app/node_modules ./node_modules CMD ["node", "dist/main.js"]
By using multi-stage builds, you keep your production image lean. I’ve seen images drop from 800MB to under 150MB just by switching to Alpine Linux. That saves disk space and reduces your attack surface.
Managing Deployments and Scaling
You don't need a fancy CI/CD pipeline to get started. I currently use a simple GitHub Action that triggers an SSH command to my VPS. It pulls the latest image, runs docker compose up -d --build, and cleans up old images.
If your project starts getting traffic, you'll eventually face the same issues I did with Laravel Horizon graceful shutdowns. If you're running background workers, make sure your Docker containers handle SIGTERM signals correctly. Otherwise, you'll lose jobs every time you deploy an update.
## Optimizing Performance on a Single Cheap VPS
When you're restricted to one server, you have to be smarter about your resource allocation. Don't let your database and application fight for CPU cycles.
- Database Placement: If you can, use a managed database service like Neon or Supabase to offload the IO stress. It keeps your VPS dedicated to the app runtime.
- Reverse Proxy: Use Nginx or Caddy. I prefer Caddy because it handles SSL certificate renewal automatically out of the box.
- Caching: If you're doing heavy database queries, fix them at the schema level first by using a proper indexing strategy for app developers. Don't reach for Redis until you're sure you actually need it.
Common Pitfalls
One thing I'm still figuring out is logging. On a single VPS, logs can fill up your disk in a few weeks if you aren't careful. I’ve had instances go down because /var/log consumed all available space. Now, I always set max-size in my Docker logging configuration:
YAML# docker-compose.yml services: app: image: my-app logging: driver: "json-file" options: max-size: "10m" max-file: "3"
This ensures that even if your app goes rogue and logs like crazy, it won't crash the entire server.
FAQ: Common Questions
Q: Should I use a managed database or host it on the VPS? A: If it's a side project, start with the database on the VPS to save money. Just ensure you have a cron job backing up your data to S3 or an R2 bucket.
Q: How do I handle downtime during deployments?
A: Use a blue-green deployment strategy or a simple docker-compose update. For most side projects, a 5-second window of downtime during a restart is perfectly acceptable.
Q: Is 1GB of RAM really enough? A: Yes, if you optimize your stack. Avoid memory-hungry runtimes if you don't need them. If you're running a heavy Node.js app, consider moving to Go or Rust for the high-concurrency parts if you hit a wall.
I’m still experimenting with automated backups. I used to rely on manual exports, but I've recently started using automated scripts that ship database snapshots to cold storage. It's not perfect, and I’m sure I’ll break something eventually, but it’s a massive step up from having no recovery plan at all. Don't over-engineer it—just make sure you can get back online if the server provider decides to reboot your instance.