Dockerize A Fullstack App on VPS
 · 3 min read
Deploy A Fullstack App to VPS with Docker
update your droplet
sudo apt update && sudo apt upgrade -y
Pull the frontend and backend code into the /opt directory i.e /opt/backend and /opt/frontend
Install Docker and certbot
sudo apt-get update
# Docker
curl -fsSL https://get.docker.com -o get-docker.sh
sh get-docker.sh
# Docker Compose
apt-get update
apt-get install docker-compose-plugin
or
sudo apt install -y docker-compose
# Certbot
apt-get install certbot
verify docker
docker --version
docker-compose --version
Map A record to your domain's DNS
Enable Docker to run on boot
sudo systemctl enable --now docker
Get SSL ertificates before running docker containers.
# Stop any running web servers first
systemctl stop nginx  # if nginx is running
# Get certs for both domains
certbot certonly --standalone -d yourdomain.com
certbot certonly --standalone -d api.yourdomain.com
Set up backend
cd /opt/backend
git pull origin main
# Back on the droplet:
# Create/edit .env file
nano .env
# Add your environment variables:
POSTGRES_USER=youruser
POSTGRES_PASSWORD=yourpassword
POSTGRES_DB=yourdb
DOMAIN=yourdomain.com
CORS_ORIGINS=https://yourdomain.com
# Start the backend services
docker-compose up -d
Here is the Dockerfile for the backend application
FROM python:3.11.1-slim
# Set env vars
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
WORKDIR /app
#install deps
RUN apt-get update \
    && apt-get install -y --no-install-recommends \
    gcc \
    libpq-dev \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/*
# install python deps
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# copy fastapi code
COPY . .
# Expose port
EXPOSE 8000
# set entry point
CMD ["uvicorn", "api.app.main:app", "--host", "0.0.0.0", "--port", "8000"]
Here is the Docker compose file for the database, environment variables and environment management. Make sure the environment variables are corectly captured in .env file
version: "3"
services:
  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/conf.d/default.conf
      - /etc/letsencrypt:/etc/letsencrypt
    depends_on:
      - frontend
      - app
  frontend:
    build: ../frontend  # Adjust this path to your frontend directory
    environment:
      - NEXT_PUBLIC_API_URL=https://api.${DOMAIN}
      - NEXT_PUBLIC_DOMAIN=${DOMAIN}
    depends_on:
      - app
  db:
    env_file: .env
    image: postgres
    environment:
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: ${POSTGRES_DB}
    volumes:
      - db-data:/var/lib/postgresql/data
  app:
    build: .
    environment:
      DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}
      DOMAIN: ${DOMAIN}
    depends_on:
      - db
volumes:
  db-data:
Here is the NGINX for SSL and request routing.
server {
    listen 80;
    server_name ${DOMAIN};
    return 301 https://$server_name$request_uri;
}
server {
    listen 443 ssl;
    server_name ${DOMAIN};
    ssl_certificate /etc/letsencrypt/live/${DOMAIN}/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/${DOMAIN}/privkey.pem;
    # Frontend
    location / {
        proxy_pass http://frontend:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
    # Backend API
    location /api {
        proxy_pass http://app:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}
Create deployment script
#!/bin/bash
# deploy.sh
# Pull latest changes
git pull
# Build and restart containers
docker-compose down
docker-compose build
docker-compose up -d
# Check logs
docker-compose logs -f
Create SSL renewal script
# Create renewal script
echo "#!/bin/bash
certbot renew
docker-compose restart nginx" > /root/renew-certs.sh
chmod +x /root/renew-certs.sh
# Add to crontab
(crontab -l 2>/dev/null; echo "0 0 1 * * /root/renew-certs.sh") | crontab -