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 -