Skip to content

Docker-Based Hosting — Complete Deployment Guide

DodaTech Updated 2026-06-20 10 min read

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:

  1. Write a Dockerfile for a simple Node.js or Python web app
  2. Create a docker-compose.yml with the app, PostgreSQL, and Redis
  3. Add an NGINX reverse proxy service with a custom config
  4. Configure named volumes for database persistence
  5. Add health checks to all services
  6. Set up environment variables for database credentials (use .env file)

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

What is the difference between Docker and a virtual machine?

Docker containers share the host OS kernel and start in seconds. VMs include a full guest OS and start in minutes. Containers use fewer resources (MB vs GB) and are ideal for microservices and web applications.

Is Docker free to use?

Docker Desktop has a paid license for commercial use in enterprises over 250 employees. Docker Engine (the command-line tools and daemon) remains free and open source under the Apache 2.0 license.

Can I use Docker in production?

Yes — Docker is production-proven at companies like Netflix, Uber, and Spotify. Use Docker Swarm or Kubernetes for orchestration at scale, set resource limits, configure log rotation, and implement health checks.

How do I persist database data in Docker?

Use named volumes mapped to the database's data directory (e.g., -v pgdata:/var/lib/<a href="/databases/postgresql/">postgresql</a>/data). Never store database data in the container's writable layer — it disappears when the container is removed.

Should I use Docker Compose or Kubernetes?

Docker Compose is ideal for single-host deployments and development environments. Kubernetes is designed for multi-host, production-grade orchestration with auto-scaling, rolling updates, and self-healing. Start with Compose, migrate to Kubernetes when needed.

Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro