/ technical / How to Deploy Without a DevOps Team
technical 9 min read

How to Deploy Without a DevOps Team

You don't need a DevOps engineer to run production apps. Docker, a VPS, and some basic knowledge. Here's the solo developer's deployment playbook.

How to Deploy Without a DevOps Team - Complete technical guide and tutorial

"You need a DevOps team for production."

I've heard this a lot. And for big companies with complex infrastructure, it's true. But for solo developers running small to medium applications? You can handle deployment yourself.

I've been deploying my own apps to production for years. No DevOps engineer. No Kubernetes cluster. No seven-figure AWS bill.

Just Docker, a VPS, and some basic knowledge.

Here's everything you need to know.

What You'll Learn:
  • Why VPS + Docker beats complicated cloud setups
  • Setting up a production server from scratch
  • Docker Compose for multi-service deployments
  • SSL, domains, and HTTPS
  • Backups and monitoring
  • What to do when things break

The Philosophy: Simple and Understandable

Before we get tactical, let me explain the approach.

Principle 1: You should understand everything.

If something breaks at 2 AM, you need to fix it. That means no black-box services you don't understand. No magic configurations copied from blog posts.

Principle 2: Simpler is more reliable.

Every additional service is another thing that can break. A single VPS running Docker Compose is easier to reason about than 12 AWS services connected with IAM roles.

Principle 3: Optimize for your scale.

You're not Netflix. You don't need Netflix infrastructure. A $20/month VPS handles more traffic than most solo projects will ever see.

With that in mind, here's the setup.

Step 1: Get a VPS

A VPS (Virtual Private Server) is a virtual machine in the cloud that you control completely.

Providers I recommend..

DigitalOcean - Great UI, good documentation, $4-6/month for basic droplets.

Hetzner - European company, incredibly cheap, excellent performance. €4.50/month for a capable server.

Linode - Solid reliability, similar pricing to DigitalOcean.

Vultr - Good global coverage, competitive pricing.

What specs do you need?

For most solo projects: 1-2 CPU cores, 1-2GB RAM, 25-50GB SSD.

That's enough to run a Django/Rails app, a database, and some background workers. Upgrade if you need to, but start small.

My typical starting point: $10-20/month server.

Step 2: Initial Server Setup

When you first SSH into your new server, there's basic setup to do.

# Update packages
apt update && apt upgrade -y

# Create a non-root user
adduser kevin
usermod -aG sudo kevin

# Set up SSH key authentication (do this before disabling passwords)
mkdir -p /home/kevin/.ssh
cp ~/.ssh/authorized_keys /home/kevin/.ssh/
chown -R kevin:kevin /home/kevin/.ssh

Secure SSH access..

# Edit SSH config
sudo nano /etc/ssh/sshd_config

# Change these settings:
PermitRootLogin no
PasswordAuthentication no

# Restart SSH
sudo systemctl restart sshd

Set up a firewall..

# Allow SSH, HTTP, HTTPS
sudo ufw allow 22
sudo ufw allow 80
sudo ufw allow 443
sudo ufw enable

This is the minimum security baseline. No root login. No password auth. Firewall blocking everything except what you need.

Step 3: Install Docker

Docker is the foundation of modern deployment. If you're not using it, you're making life harder.

# Install Docker
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh

# Add your user to docker group
sudo usermod -aG docker kevin

# Install Docker Compose
sudo apt install docker-compose-plugin

Log out and back in for group changes to take effect.

Verify installation..

docker --version
docker compose version

Step 4: Docker Compose for Your App

Here's a real production Docker Compose setup I use.

# docker-compose.yml
version: '3.8'

services:
  web:
    build: .
    restart: always
    environment:
      - DATABASE_URL=postgres://user:pass@db:5432/appdb
      - SECRET_KEY=${SECRET_KEY}
      - DEBUG=false
    depends_on:
      - db
      - redis
    expose:
      - "8000"

  db:
    image: postgres:15
    restart: always
    volumes:
      - postgres_data:/var/lib/postgresql/data
    environment:
      - POSTGRES_USER=user
      - POSTGRES_PASSWORD=pass
      - POSTGRES_DB=appdb

  redis:
    image: redis:alpine
    restart: always

  nginx:
    image: nginx:alpine
    restart: always
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
      - ./ssl:/etc/nginx/ssl
    depends_on:
      - web

volumes:
  postgres_data:

Key points..

  • restart: always - Services restart automatically after crashes or reboots
  • expose vs ports - Internal services use expose, only nginx binds to public ports
  • volumes - Database data persists in a named volume
  • environment variables - Secrets passed via .env file, not committed to git

Step 5: Nginx Configuration

Nginx sits in front of your app, handling SSL and proxying requests.

# nginx.conf
events {
    worker_connections 1024;
}

http {
    server {
        listen 80;
        server_name yourdomain.com;

        location /.well-known/acme-challenge/ {
            root /var/www/certbot;
        }

        location / {
            return 301 https://$server_name$request_uri;
        }
    }

    server {
        listen 443 ssl;
        server_name yourdomain.com;

        ssl_certificate /etc/nginx/ssl/fullchain.pem;
        ssl_certificate_key /etc/nginx/ssl/privkey.pem;

        location / {
            proxy_pass http://web:8000;
            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;
        }

        location /static/ {
            alias /app/static/;
        }
    }
}

This config..

  • Redirects HTTP to HTTPS
  • Handles SSL termination
  • Proxies requests to your app
  • Serves static files directly

Step 6: SSL with Let's Encrypt

Free SSL certificates with automatic renewal.

First time setup..

# Install Certbot
sudo apt install certbot

# Get certificate (stop nginx first)
docker compose stop nginx
sudo certbot certonly --standalone -d yourdomain.com
docker compose up -d nginx

# Copy certs to your ssl folder
sudo cp /etc/letsencrypt/live/yourdomain.com/fullchain.pem ./ssl/
sudo cp /etc/letsencrypt/live/yourdomain.com/privkey.pem ./ssl/

Auto-renewal with cron..

# Add to crontab
0 0 1 * * certbot renew --pre-hook "docker compose stop nginx" --post-hook "docker compose up -d nginx"

Certs renew monthly, nginx restarts with new certs.

Step 7: Deployment Workflow

How do you actually deploy updates?

Option 1: Manual SSH deploy

# On your server
cd /app
git pull
docker compose build
docker compose up -d

Simple. Works. Good enough for solo projects.

Option 2: GitHub Actions

# .github/workflows/deploy.yml
name: Deploy

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Deploy to server
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: ${{ secrets.SERVER_USER }}
          key: ${{ secrets.SSH_KEY }}
          script: |
            cd /app
            git pull
            docker compose build
            docker compose up -d

Push to main, deployment happens automatically.

Step 8: Backups

Database backups are critical.

# backup.sh
#!/bin/bash
DATE=$(date +%Y%m%d)
docker compose exec -T db pg_dump -U user appdb > /backups/db_$DATE.sql
gzip /backups/db_$DATE.sql

# Keep only last 7 days
find /backups -name "*.gz" -mtime +7 -delete

Run this daily with cron. Store backups somewhere off-server (S3, Backblaze B2, another VPS).

# Crontab entry
0 3 * * * /app/backup.sh

Test your restores. A backup you've never tested isn't a backup.

Step 9: Monitoring

You need to know when things break.

Basic uptime monitoring..

UptimeRobot (free) pings your site every 5 minutes and alerts you when it's down.

Error tracking..

Sentry (free tier) captures application errors with full stack traces and context.

Log access..

# View logs
docker compose logs -f web
docker compose logs --tail=100 nginx

# System resources
htop
df -h

For solo projects, this is usually enough. You don't need Datadog or elaborate observability platforms.

Step 10: What To Do When Things Break

They will break. Here's the debugging playbook.

App won't start..

# Check logs
docker compose logs web

# Check if containers are running
docker compose ps

# Restart everything
docker compose down
docker compose up -d

Out of disk space..

# Check disk usage
df -h

# Clean up Docker
docker system prune -a

# Find large files
du -sh /* | sort -hr | head -10

Out of memory..

# Check memory usage
free -h

# Find memory-hungry processes
ps aux --sort=-%mem | head -10

# Add swap if needed
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile

Can't SSH in..

Use your VPS provider's console access. All providers have a web-based console for emergencies.

Security Checklist

Security Basics (Don't Skip These):
  • SSH key auth only, no passwords
  • No root login
  • Firewall enabled, only necessary ports open
  • Regular system updates (apt update && apt upgrade)
  • Strong database passwords
  • Secrets in environment variables, not in code
  • HTTPS everywhere

I learned most of this the hard way when my server got hacked. Don't skip the basics.

When to Upgrade

This setup works until it doesn't. Signs you need more..

High traffic. If your single server can't handle the load, consider a load balancer and multiple app servers.

High availability requirements. If downtime is unacceptable, you need redundancy.

Complex infrastructure. If you have 10+ services, maybe Kubernetes makes sense. Maybe.

But for 90% of solo developer projects? A single VPS with Docker Compose is plenty.

FAQ

Why not just use Heroku/Railway/Render?

They're great for getting started. But they're expensive at scale. A $20 VPS handles what would cost $100+ on PaaS platforms.

What about Kubernetes?

Overkill for solo projects. Kubernetes solves problems you don't have yet. When you have 50 services and a team of 10, revisit it.

How do I handle database migrations?

Run them as part of your deploy script, before starting the new app version. docker compose exec web python manage.py migrate

What if I need zero-downtime deploys?

For solo projects, a few seconds of downtime during deploy is usually fine. If you need zero-downtime, look into blue-green deployments or rolling updates.

Should I use managed databases?

They're convenient and handle backups automatically. More expensive than self-managed. Trade-off depends on how much you value your time vs. money.

Final Thoughts

DevOps is a real skill. There's depth I haven't covered here.

But you don't need to be a DevOps expert to deploy production apps. You need the basics. Server setup. Docker. SSL. Backups. Monitoring.

Master those, and you can run real products for real users on infrastructure you understand and control.

Start with a VPS and Docker Compose. Learn as you go. Upgrade when you need to.

That's the solo developer way.