Lightsail: Docker Deployment
Deploy containerized applications to AWS Lightsail. Use Docker for consistent, reproducible deployments with easy rollbacks.
What you'll accomplish
Your Dockerized application running on Lightsail with automatic restarts and SSL
Prerequisites
- AWS Lightsail instance running (previous lesson)
- SSH access configured
- Docker installed locally
- Basic Docker knowledge
Why Docker on Lightsail?
Docker provides several advantages over traditional deployment:
| Traditional | Docker |
|---|---|
| Install dependencies on server | Dependencies bundled in image |
| "Works on my machine" issues | Identical everywhere |
| Manual rollbacks | Switch to previous image tag |
| Complex multi-app setup | Isolated containers |
| Environment drift over time | Immutable infrastructure |
Note
Docker adds a learning curve but pays off quickly. Once set up, deployments become simpler and more reliable.
Step 1: Prepare Your Lightsail Instance
1.1 Connect to Your Server
bashssh ubuntu@YOUR_STATIC_IP
1.2 Install Docker
If you created a plain Ubuntu instance, install Docker:
bash# Update system sudo apt update && sudo apt upgrade -y # Install Docker curl -fsSL https://get.docker.com | sudo sh # Add your user to docker group (no sudo needed for docker commands) sudo usermod -aG docker ubuntu # Log out and back in for group change to take effect exit
Reconnect and verify:
bashssh ubuntu@YOUR_STATIC_IP # Verify Docker works docker --version docker run hello-world
1.3 Install Docker Compose
bash# Install Docker Compose plugin sudo apt install -y docker-compose-plugin # Verify docker compose version
Note
Lightsail also offers a "Docker on Ubuntu" blueprint that comes with Docker pre-installed. Select it when creating your instance to skip manual installation.
Step 2: Create a Dockerfile
On your local machine, create a Dockerfile for your application.
Node.js Example
dockerfile# Dockerfile FROM node:20-alpine # Set working directory WORKDIR /app # Copy package files first (for better caching) COPY package*.json ./ # Install production dependencies RUN npm ci --only=production # Copy application code COPY . . # Build if needed (for Next.js, etc.) RUN npm run build # Expose the port your app uses EXPOSE 3000 # Set environment ENV NODE_ENV=production # Start the application CMD ["npm", "start"]
Python/Flask Example
dockerfileFROM python:3.11-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . EXPOSE 5000 CMD ["gunicorn", "--bind", "0.0.0.0:5000", "app:app"]
Create .dockerignore
text# .dockerignore node_modules .git .gitignore .env.local .env.development *.log .DS_Store Dockerfile docker-compose*.yml README.md .next dist coverage
Step 3: Build and Test Locally
Before deploying, verify your container works locally:
Step 4: Push to a Container Registry
You need somewhere to store your Docker image. Options:
| Registry | Pros | Cons |
|---|---|---|
| Docker Hub | Free tier, simple | Public by default on free |
| AWS ECR | Private, AWS integration | Requires AWS CLI setup |
| GitHub Container Registry | Free for public repos | Tied to GitHub |
Option A: Docker Hub (Simplest)
Option B: AWS ECR (Private)
bash# Create ECR repository (run once) aws ecr create-repository --repository-name myapp --region us-east-1 # Get login command aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin YOUR_ACCOUNT_ID.dkr.ecr.us-east-1.amazonaws.com # Tag for ECR docker tag myapp YOUR_ACCOUNT_ID.dkr.ecr.us-east-1.amazonaws.com/myapp:latest # Push docker push YOUR_ACCOUNT_ID.dkr.ecr.us-east-1.amazonaws.com/myapp:latest
Note
Find your AWS Account ID in the AWS Console (top right dropdown) or run aws sts get-caller-identity.
Step 5: Deploy to Lightsail
5.1 Pull and Run (Simple Method)
SSH into your server and pull the image:
bashssh ubuntu@YOUR_STATIC_IP # Pull the image docker pull yourusername/myapp:latest # Run the container docker run -d \ --name myapp \ --restart unless-stopped \ -p 3000:3000 \ -e NODE_ENV=production \ -e DATABASE_URL="your-database-url" \ yourusername/myapp:latest # Verify it's running docker ps docker logs myapp
5.2 Using Docker Compose (Recommended)
Create a docker-compose.yml on the server:
bashssh ubuntu@YOUR_STATIC_IP # Create app directory mkdir -p ~/myapp cd ~/myapp # Create docker-compose.yml nano docker-compose.yml
Add this configuration:
yamlversion: '3.8' services: app: image: yourusername/myapp:latest container_name: myapp restart: unless-stopped ports: - "3000:3000" environment: - NODE_ENV=production env_file: - .env healthcheck: test: ["CMD", "curl", "-f", "http://localhost:3000/api/health"] interval: 30s timeout: 10s retries: 3 start_period: 40s # Optional: Add a database # postgres: # image: postgres:15-alpine # container_name: myapp-db # restart: unless-stopped # volumes: # - postgres_data:/var/lib/postgresql/data # environment: # - POSTGRES_USER=myapp # - POSTGRES_PASSWORD=secretpassword # - POSTGRES_DB=myapp # ports: # - "5432:5432" # volumes: # postgres_data:
Create the .env file:
bashnano .env
envNODE_ENV=production DATABASE_URL=postgresql://user:pass@localhost:5432/mydb API_KEY=your-secret-key
Start the application:
bash# Start in detached mode docker compose up -d # View logs docker compose logs -f # Check status docker compose ps
Step 6: Set Up Nginx Reverse Proxy
6.1 Install Nginx
bashsudo apt install -y nginx sudo systemctl enable nginx
6.2 Configure Nginx
bashsudo nano /etc/nginx/sites-available/myapp
nginxserver { listen 80; server_name YOUR_DOMAIN_OR_IP; location / { proxy_pass http://localhost:3000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; 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; proxy_cache_bypass $http_upgrade; proxy_read_timeout 86400; } }
Enable the site:
bashsudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/ sudo rm /etc/nginx/sites-enabled/default sudo nginx -t sudo systemctl reload nginx
6.3 Alternative: Nginx in Docker
You can also run Nginx as a Docker container:
yaml# docker-compose.yml with Nginx version: '3.8' services: app: image: yourusername/myapp:latest container_name: myapp restart: unless-stopped expose: - "3000" environment: - NODE_ENV=production env_file: - .env nginx: image: nginx:alpine container_name: nginx restart: unless-stopped ports: - "80:80" - "443:443" volumes: - ./nginx.conf:/etc/nginx/nginx.conf:ro - ./certbot/conf:/etc/letsencrypt:ro - ./certbot/www:/var/www/certbot:ro depends_on: - app
Step 7: Add SSL with Let's Encrypt
Note
SSL requires a domain name. If you're using just an IP address, skip this step.
Using Certbot (Nginx installed on host)
bash# Install Certbot sudo apt install -y certbot python3-certbot-nginx # Get certificate sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com # Test auto-renewal sudo certbot renew --dry-run
Using Docker (Nginx in container)
Create a script to manage certificates:
bash# init-letsencrypt.sh #!/bin/bash domains=(yourdomain.com www.yourdomain.com) email="[email protected]" # Create directories mkdir -p ./certbot/conf ./certbot/www # Get initial certificate docker run --rm \ -v $(pwd)/certbot/conf:/etc/letsencrypt \ -v $(pwd)/certbot/www:/var/www/certbot \ certbot/certbot certonly \ --webroot \ --webroot-path=/var/www/certbot \ --email $email \ --agree-tos \ --no-eff-email \ -d ${domains[0]} -d ${domains[1]}
Step 8: Create Deployment Script
Automate your deployment process.
On Your Local Machine
Step 9: Useful Docker Commands
Managing Containers
bash# View running containers docker ps # View all containers (including stopped) docker ps -a # Stop a container docker stop myapp # Start a container docker start myapp # Restart a container docker restart myapp # Remove a container docker rm myapp # View container logs docker logs myapp docker logs -f myapp # Follow logs docker logs --tail 100 myapp # Last 100 lines
Docker Compose Commands
bash# Start services docker compose up -d # Stop services docker compose down # View logs docker compose logs -f # Restart a specific service docker compose restart app # Pull latest images docker compose pull # Rebuild and restart docker compose up -d --build
Cleanup
bash# Remove unused images docker image prune -f # Remove all unused resources docker system prune -f # Remove everything (including volumes - CAREFUL!) docker system prune -a --volumes
Step 10: Zero-Downtime Deployments
For production, use rolling updates:
yaml# docker-compose.yml with healthcheck version: '3.8' services: app: image: yourusername/myapp:latest deploy: replicas: 2 update_config: parallelism: 1 delay: 10s failure_action: rollback healthcheck: test: ["CMD", "curl", "-f", "http://localhost:3000/api/health"] interval: 10s timeout: 5s retries: 3 start_period: 30s
Deploy with:
bashdocker compose up -d --scale app=2
Rollbacks
One of Docker's best features - easy rollbacks:
bash# Deploy a specific version docker compose down # Edit docker-compose.yml to use yourusername/myapp:v1.0.0 docker compose up -d # Or pull and run specific tag docker pull yourusername/myapp:v1.0.0 docker stop myapp docker rm myapp docker run -d --name myapp ... yourusername/myapp:v1.0.0
Monitoring
Basic Monitoring
bash# Container resource usage docker stats # Container processes docker top myapp # Container details docker inspect myapp
Log Management
For production, consider using a log driver:
yaml# docker-compose.yml services: app: image: yourusername/myapp:latest logging: driver: "json-file" options: max-size: "10m" max-file: "3"
Troubleshooting
Container Won't Start
bash# Check logs docker logs myapp # Run interactively to debug docker run -it yourusername/myapp:latest /bin/sh
Port Already in Use
bash# Find what's using the port sudo lsof -i :3000 # Kill the process or change your port
Out of Disk Space
bash# Check disk usage df -h # Clean up Docker docker system prune -a
Image Pull Fails
bash# Re-login to registry docker login # For ECR aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin YOUR_ACCOUNT_ID.dkr.ecr.us-east-1.amazonaws.com
Cost Summary
| Component | Cost |
|---|---|
| Lightsail Instance ($5 plan) | $5/month |
| Docker Hub (free tier) | $0 |
| ECR (first 500MB free) | ~$0-1/month |
| SSL Certificate | Free (Let's Encrypt) |
| Total | ~$5-6/month |
What's Next?
You now have a containerized deployment on Lightsail. For larger applications or when you need auto-scaling, consider:
- ECS Fargate - AWS-managed container orchestration
- App Runner - Even simpler container deployment
- Kubernetes (EKS) - For complex microservices
Note
Checkpoint: Visit http://YOUR_STATIC_IP or https://yourdomain.com to verify your Dockerized application is running.