Docker-Based Hosting ā Complete Deployment Guide
In this tutorial, you'll learn about Docker. We cover key concepts, practical examples, and best practices.
Docker hosting packages apps into portable containers that run identically across development and production, eliminating deployment inconsistencies.
In this tutorial, you will learn how to write Dockerfiles for web applications, build and run containers, use Docker Compose to orchestrate multi-service stacks (app + database + cache), set up persistent storage with volumes, deploy behind an NGINX reverse proxy, and implement production-ready deployment patterns. DodaTech uses Docker containers to deploy Doda Browser API services and Durga Antivirus Pro backend microservices.
What You'll Learn
By the end of this guide, you will containerize a web application with a custom Dockerfile, orchestrate an app-database-cache stack with Docker Compose, manage persistent data with volumes, and deploy behind a reverse proxy for production.
Why Docker Hosting Matters
Docker adoption is over 80% among professional developers. Containers start in seconds, use fewer resources than virtual machines, and ensure identical environments across your entire team. For web hosting, Docker eliminates configuration drift and simplifies scaling whether you deploy on AWS EC2, Linux servers, or local development machines.
Docker Hosting Learning Path
flowchart LR
A[Dockerfile Basics] --> B[Building & Running]
B --> C[Docker Compose]
C --> D[Volumes & Data]
D --> E[Networking & Proxy]
E --> F[Production Patterns]
F --> G{You Are Here}
style G fill:#f90,color:#fff
Writing a Dockerfile
A Dockerfile is a recipe that tells Docker how to build your container image.
Basic Node.js Web App Dockerfile
# Use an official Node.js runtime as base image
FROM node:20-alpine
# Set working directory inside the container
WORKDIR /app
# Copy package files and install dependencies (layer caching)
COPY package*.json ./
RUN npm ci --only=production
# Copy application source code
COPY . .
# Expose the application port
EXPOSE 3000
# Define the startup command
CMD ["node", "server.js"]
Explanation of each instruction
| Instruction | Purpose |
|---|---|
FROM |
Specifies the base image ā Alpine variants are minimal (~5 MB) |
WORKDIR |
Sets the working directory for all subsequent commands |
COPY package*.json ./ |
Copies dependency manifest first ā Docker caches this layer |
RUN npm ci |
Installs exact versions from package-lock.json for reproducible builds |
COPY . . |
Copies the rest of the source code (after deps are cached) |
EXPOSE |
Documents the port ā does NOT publish it (that happens at docker run) |
CMD |
The command that runs when the container starts |
Build and Run
# Build the image (tag it for easy reference)
docker build -t my-web-app:v1 .
# Run the container
docker run -d --name my-app -p 8080:3000 my-web-app:v1
Expected output
# Verify the container is running
docker ps
# CONTAINER ID IMAGE COMMAND PORTS NAMES
# a1b2c3d4e5f6 my-web-app:v1 "docker-entrypoint.sā¦" 0.0.0.0:8080->3000/tcp my-app
# Access the application
curl http://localhost:8080
# Expected: Welcome to my Docker web app!
Docker Compose ā Multi-Service Stacks
Docker Compose defines and runs multi-container applications with a single YAML file. Instead of starting containers one by one, you declare the entire stack.
Web App + PostgreSQL + Redis
version: '3.8'
services:
web:
build: .
ports:
- "8080:3000"
environment:
- DATABASE_URL=postgres://user:pass@db:5432/appdb
- REDIS_URL=redis://cache:6379
depends_on:
- db
- cache
volumes:
- .:/app
- /app/node_modules
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
POSTGRES_DB: appdb
volumes:
- pgdata:/var/lib/postgresql/data
ports:
- "5432:5432"
cache:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redisdata:/data
volumes:
pgdata:
redisdata:
Start the Stack
# Build and start all services
docker compose up -d
# View logs from all services
docker compose logs -f
# Run database migrations
docker compose exec web npx prisma migrate deploy
Expected output
docker compose ps
# NAME IMAGE STATUS PORTS
# docker-hosting-web-1 docker-hosting-web Up 0.0.0.0:8080->3000/tcp
# docker-hosting-db-1 postgres:16-alpine Up 0.0.0.0:5432->5432/tcp
# docker-hosting-cache-1 redis:7-alpine Up 0.0.0.0:6379->6379/tcp
Volumes ā Persistent Data
Containers are ephemeral ā when removed, all data inside them disappears. Volumes store data outside the container filesystem.
Named vs Bind Mount Volumes
| Type | Syntax | Stored In | Use Case |
|---|---|---|---|
| Named volume | -v pgdata:/var/lib/<a href="/databases/postgresql/">postgresql</a>/data |
Docker's managed storage (/var/lib/docker/volumes/) |
Database data, persistent state |
| Bind mount | -v /home/user/code:/app |
Host filesystem path | Development with live code reload |
| tmpfs mount | --tmpfs /app/cache |
Host memory only | Temporary files, session data |
Manage Volumes
# List all volumes
docker volume ls
# Inspect a volume (shows mount point on host)
docker volume inspect pgdata
# [
# {
# "Name": "docker-hosting_pgdata",
# "Mountpoint": "/var/lib/docker/volumes/docker-hosting_pgdata/_data]
# }
# ]
# Remove unused volumes (ā ļø deletes all data)
docker volume prune
Networking & Reverse Proxy
Containers communicate over Docker networks. Each service in a docker-compose.yml gets a DNS-resolvable hostname (e.g., db, cache).
Exposing Services Behind NGINX
# nginx.conf ā reverse proxy for Docker services
upstream web_app {
server web:3000;
}
upstream api {
server api:4000;
}
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://web_app;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
location /api/ {
proxy_pass http://api/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
Compose Stack with NGINX
version: '3.8'
services:
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
- ./ssl:/etc/nginx/ssl:ro
depends_on:
- web
- api
web:
build: ./web
expose:
- "3000"
api:
build: ./api
expose:
- "4000"
Production Deployment Patterns
Zero-Downtime Deployment
# Build new image
docker build -t my-web-app:v2 .
# Start new container alongside the old one
docker run -d --name my-app-v2 --network app-net \
-e "DATABASE_URL=postgres://user:pass@db:5432/appdb" \
my-web-app:v2
# Wait for health check, then switch traffic
# (Update reverse proxy config or use a load balancer)
# Remove old container
docker stop my-app && docker rm my-app
Health Checks in Docker Compose
services:
web:
build: .
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 15s
Common Errors
1. Port Already in Use (EADDRINUSE)
docker: Error response from daemon: driver failed programming external
connectivity on endpoint: Bind for 0.0.0.0:8080 failed: port is already allocated.
Stop the container using the port or change the host port mapping: docker stop $(docker ps -q) or use -p 8081:3000.
2. Permission Denied When Reading Volume Mounts
# Error: could not access bind mount source
# Fix: ensure the host directory has correct ownership
sudo chown -R 1000:1000 ./data
Many container images run as non-root users (UID 1000). Match the host directory ownership to the container's user ID.
3. Docker Compose Services Can't Reach Each Other
Services communicate using service names as hostnames. Verify they are on the same network:
docker compose exec web ping db
# Expected: 64 bytes from db (172.19.0.2)
If the network is missing, add networks: section to the compose file.
4. Container Exits Immediately After Starting
The application crashed on startup. View logs:
docker logs my-app
# Error: Cannot find module 'express'
# Fix: COPY package*.json and RUN npm install in Dockerfile
5. Image Build Takes Too Long
Docker rebuilds every layer when the context changes. Optimize caching by ordering COPY instructions from least to most frequently changing:
COPY package*.json ./ # rarely changes
RUN npm install # cached unless package files change
COPY . . # changes often ā rebuilds only this layer
6. "no space left on device" in Docker
Docker accumulates unused images, containers, and volumes. Clean up:
docker system prune -a --volumes # ā ļø removes ALL unused resources
Check disk usage first: docker system df
7. Application Logs Missing or Truncated
Containers output logs to stdout/stderr. If logs are too large, configure log rotation in daemon.json:
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}
Practice Questions
1. What is the difference between a Docker image and a container?
An image is a read-only template (like a class in programming). A container is a running instance of that image (like an object). You can have multiple containers from the same image.
2. Why use Docker Compose instead of running containers individually?
Docker Compose defines the entire application stack (app, database, cache) in a single YAML file. It creates networks, manages dependencies, and ensures services start in the correct order with one command.
3. What is the purpose of a named volume?
A named volume persists data outside the container's writable layer. When the container is removed and recreated, the data in the volume survives. This is essential for databases like PostgreSQL and MySQL.
4. How do you deploy a Docker container with zero downtime?
Start a new container with the updated image alongside the old one. Once the new container passes health checks, update the reverse proxy or load balancer to route traffic to the new container, then stop the old one.
5. Challenge: Write a multi-stage Docker build
Write a Dockerfile that builds a Go binary in a golang:1.22 build stage, then copies only the compiled binary into a minimal scratch or alpine image for the final runtime stage. This reduces the final image from ~800 MB to ~15 MB.
Mini Project: Docker Hosting Stack
Build a complete Docker-based hosting environment for a web application:
- Write a Dockerfile for a simple Node.js or Python web app
- Create a
docker-compose.ymlwith the app, PostgreSQL, and Redis - Add an NGINX reverse proxy service with a custom config
- Configure named volumes for database persistence
- Add health checks to all services
- Set up environment variables for database credentials (use
.envfile)
Test the stack:
# Build and start the stack in detached mode
docker compose up -d
# Verify all services are running
docker compose ps
# Expected: 4/4 services running (nginx, web, db, cache)
# Test the application through NGINX
curl -I http://localhost
# Expected: HTTP/1.1 200 OK
# Test database connectivity from the app container
docker compose exec web node -e "require('pg').Client('postgres://user:pass@db:5432/appdb')"
# Simulate failure and recovery
docker compose stop db
docker compose start db
# Expected: Data persists after restart
# Clean up
docker compose down -v # ā ļø removes volumes
This stack matches the pattern DodaTech uses for Doda Browser API services and Durga Antivirus Pro backend microservices.
FAQ
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro