Beginner

Docker for Web Developers

Why Docker, setting up your environment, connecting source code, custom images and Docker Compose.

Table of Contents

  1. Course Overview
  2. Why Use Docker as a Developer?
  3. Setting Up Your Development Environment
  4. Connecting Source Code to a Container
  5. Building Custom Images with Dockerfile
  6. Communication Between Docker Containers
  7. Managing Containers with Docker Compose
  8. Reference Tables
  9. Architecture Diagrams

1. Course Overview

This course covers using Docker in a web development context:

  • Why Docker improves developer productivity and guarantees consistency across environments
  • Docker Desktop: installation and configuration
  • Images and containers: the layered file system
  • Volumes: persisting data and connecting source code to a container
  • Dockerfile: creating custom images, multi-stage builds
  • Docker Hub: publishing and sharing images
  • Container networking: making containers communicate
  • Docker Compose: orchestrating multiple services with a single YAML file

2. Why Use Docker as a Developer?

What is Docker?

Docker is a lightweight, open, and secure platform that simplifies building, deploying, and running applications.

“Docker is a shipping container system for code.”

ConceptDescription
ImageRead-only template composed of layered file systems. Used to share common files and create container instances.
ContainerRunning instance of an image. Adds a thin writable layer on top of the image.

Benefits for Developers

  1. Accelerate developer onboarding — share the complete environment via a few commands
  2. Eliminate app conflicts — different Node.js, Python, .NET versions coexist without conflicts
  3. Environment consistency — a container that works locally works the same way in staging and production
  4. Ship software faster — no hours setting up environments to test a feature

Images vs Containers

Image (read-only)
├── Layer 1: OS base (Alpine, Ubuntu, etc.)
├── Layer 2: Runtime (Node.js, .NET, etc.)
├── Layer 3: Application dependencies
└── Layer 4: Source code / binaries

Container (running)
└── Thin writable layer (logs, temp files)
    └── [points to image layers]

A container is not a virtual machine — it shares the host kernel via Docker Engine, making it much lighter.


3. Setting Up Your Development Environment

Installing Docker Desktop

Docker Desktop includes:

  • Docker Engine — the container runtime
  • Docker CLI Client — command-line interface
  • Docker Compose — multi-container orchestration

On Windows

  • WSL 2 (recommended): Windows Subsystem for Linux
  • Hyper-V: alternative if WSL isn’t available

Configure WSL via %USERPROFILE%\.wslconfig:

[wsl2]
memory=8GB
processors=4

Essential Docker Commands

docker pull nginx:alpine          # Pull an image from Docker Hub
docker run -p 8080:80 nginx:alpine  # Start a container
docker ps                          # List running containers
docker ps -a                       # List all containers (including stopped)
docker images                      # List local images
docker rm <container_id>           # Remove a container
docker rmi <image_id>              # Remove an image
docker <command> --help            # Get help for any command

4. Connecting Source Code to a Container

The Layered File System

Image (read-only layers)
┌─────────────────────────────┐
│ Layer 4: Source code        │
├─────────────────────────────┤
│ Layer 3: npm packages       │
├─────────────────────────────┤
│ Layer 2: Node.js runtime    │
├─────────────────────────────┤
│ Layer 1: Alpine Linux base  │
└─────────────────────────────┘
         ↓ docker run
Container (adds thin R/W layer)
┌─────────────────────────────┐
│ Writable layer (logs, etc.) │ ← deleted when container is removed
└─────────────────────────────┘

Key points:

  • Image layers are immutable (read-only)
  • The container adds a thin R/W layer on top
  • If the container is deleted → the R/W layer disappears with it
  • Layers are cached during build → rebuilds are fast if early layers haven’t changed

Volumes and Containers

A volume is a special directory on the Docker host that containers can use to persist data outside the container.

Option 1: Docker-managed volume

docker run -p 8080:3000 -v /var/www node
docker inspect <container_name>  # Find host path

Option 2: Custom volume (bind mount)

# Linux / Mac / WSL:
docker run -p 8080:3000 -v $(pwd):/var/www node

# Windows PowerShell:
docker run -p 8080:3000 -v ${PWD}:/var/www node

Remove container and its volume

docker rm -v <container_name_or_id>

Development Flow with Volumes

# Node.js Express in a container
docker run -p 8080:3000 \
  -v $(pwd):/var/www \
  -w /var/www \
  node \
  npm start

# ASP.NET Core in a container
docker run -p 8080:5000 \
  -v $(pwd):/app \
  -w /app \
  -e ASPNETCORE_URLS="http://*:5000" \
  -e DOTNET_USE_POLLING_FILE_WATCHER=1 \
  mcr.microsoft.com/dotnet/sdk \
  dotnet watch run

Note: ASPNETCORE_URLS="http://*:5000" uses * instead of localhost so port forwarding works correctly from the container.


5. Building Custom Images with Dockerfile

Dockerfile Anatomy

# FROM: base image (foundation)
FROM node:alpine

# LABEL: metadata
LABEL author="Web Developer"

# ENV: environment variables
ENV NODE_ENV=production
ENV PORT=3000

# WORKDIR: working directory in container
WORKDIR /var/www

# COPY: copy files from host to image
COPY package*.json ./
COPY . .

# RUN: execute commands during build
RUN npm install --only=production

# EXPOSE: document the port the app listens on
EXPOSE 3000

# ENTRYPOINT: main command
ENTRYPOINT ["node", "server.js"]

Build an image from a Dockerfile

# Syntax: docker build -t <tag> <build_context>
docker build -t my-node-app:1.0 .

# With a Dockerfile at a custom path
docker build -t my-node-app:1.0 -f ./docker/Dockerfile .

The . at the end is crucial — it defines the build context.

Custom Node.js Image

FROM node:alpine

LABEL author="Web Developer"

ENV NODE_ENV=production
ENV PORT=3000

WORKDIR /var/www

COPY package*.json ./
RUN npm install --only=production

COPY . .

EXPOSE 3000

ENTRYPOINT ["node", "server.js"]
docker build -t myusername/nodeapp:1.0 .
docker run -d -p 8080:3000 --name my-node-app myusername/nodeapp:1.0
docker logs my-node-app

Multi-stage Dockerfile

┌──────────────────────────────────────┐
│  Stage 1: BUILD                      │
│  Image: mcr.microsoft.com/dotnet/sdk │
│  • Restore packages                  │
│  • Compile code                      │
│  • Publish binaries                  │
└──────────────────────┬───────────────┘
                       │ COPY --from=build
                       ▼
┌──────────────────────────────────────┐
│  Stage 2: FINAL (production)         │
│  Image: mcr.microsoft.com/dotnet/aspnet│
│  • Only binaries                     │
│  • Lightweight, no SDK               │
└──────────────────────────────────────┘

ASP.NET Core multi-stage:

FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /build

COPY *.csproj ./
RUN dotnet restore

COPY . .
RUN dotnet publish -c Release -o /app/publish

FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS final
WORKDIR /app

ENV ASPNETCORE_URLS="http://*:5000"

COPY --from=build /app/publish .

EXPOSE 5000

ENTRYPOINT ["dotnet", "MyApp.dll"]

Node.js multi-stage:

FROM node:20-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:20-alpine AS production
WORKDIR /app
ENV NODE_ENV=production
COPY package*.json ./
RUN npm ci --only=production
COPY --from=build /app/dist ./dist
EXPOSE 3000
CMD ["node", "dist/server.js"]
AdvantageDescription
Lightweight imageFinal image contains no SDK or build tools
SecuritySmaller attack surface (no debug tools in prod)
Simplified CI/CDBuild server only needs Docker — no SDK installed

Publishing an Image to Docker Hub

docker login
docker tag myapp:1.0 myusername/myapp:1.0
docker push myusername/myapp:1.0
docker pull myusername/myapp:1.0

Other Registries

RegistryTag Format
Docker Hubusername/image:tag
Azure Container Registrymyacr.azurecr.io/image:tag
AWS ECR123456789.dkr.ecr.us-east-1.amazonaws.com/image:tag
GCP Artifact Registryus-central1-docker.pkg.dev/project/repo/image:tag

6. Communication Between Docker Containers

Container Networks

By default, containers are isolated and cannot communicate. Place them in the same Docker network to enable communication.

Bridge Network in Action

# 1. Create a custom bridge network
docker network create --driver bridge my-network

# 2. Start MongoDB container on this network
docker run -d \
  --net=my-network \
  --name=mongodb \
  mongo

# 3. Start Node.js container on the same network
docker run -d \
  -p 8080:3000 \
  --net=my-network \
  --name=nodeapp \
  my-node-image

# Node.js container can now reach MongoDB via the name "mongodb"
# mongodb://mongodb:27017/mydb
docker network ls                            # List available networks
docker network inspect my-network            # Inspect a network
docker network connect my-network my-container  # Connect existing container

7. Managing Containers with Docker Compose

docker-compose.yml File

version: "3.9"

services:
  node:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "8080:3000"
    networks:
      - webnet
    environment:
      - NODE_ENV=development
    volumes:
      - .:/var/www/app
      - /var/www/app/node_modules
    depends_on:
      - mongodb

  mongodb:
    image: mongo
    ports:
      - "27017:27017"
    networks:
      - webnet
    volumes:
      - mongo-data:/data/db

networks:
  webnet:
    driver: bridge

volumes:
  mongo-data:

Complete Example: Frontend + Backend + Database

version: "3.9"

services:
  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    depends_on:
      - frontend
      - api
    networks:
      - webnet

  frontend:
    build:
      context: ./frontend
    environment:
      - NODE_ENV=development
    volumes:
      - ./frontend:/app
      - /app/node_modules
    networks:
      - webnet

  api:
    build:
      context: ./api
    environment:
      - NODE_ENV=development
      - MONGO_URI=mongodb://mongodb:27017/appdb
    volumes:
      - ./api:/app
      - /app/node_modules
    depends_on:
      - mongodb
    networks:
      - webnet

  mongodb:
    image: mongo:7
    environment:
      - MONGO_INITDB_ROOT_USERNAME=admin
      - MONGO_INITDB_ROOT_PASSWORD=secret
    volumes:
      - mongo-data:/data/db
    networks:
      - webnet

networks:
  webnet:
    driver: bridge

volumes:
  mongo-data:

Docker Compose Commands

# Build all service images
docker compose build

# Start all services (background with -d)
docker compose up
docker compose up -d

# Stop and remove containers, networks, anonymous volumes
docker compose down

# Stop and remove ALSO named volumes
docker compose down -v

# View logs from all services
docker compose logs

# Stream logs from a specific service
docker compose logs -f node

# List containers managed by Compose
docker compose ps

# Rebuild images AND restart
docker compose up --build

# Execute a command in a service
docker compose exec node sh

# Stop services without removing containers
docker compose stop

# Restart a specific service
docker compose restart node

8. Reference Tables

Essential Docker Commands

CommandDescription
docker pull <image>Pull image from registry
docker run <image>Start a container
docker run -dRun in detached mode (background)
docker run -p <host>:<container>Port mapping
docker run -v <host>:<container>Volume mount
docker run -w <path>Set working directory
docker run --name <name>Assign name
docker run --net <network>Connect to network
docker psList running containers
docker ps -aList all containers
docker stop <container>Stop container
docker rm <container>Remove container
docker rm -v <container>Remove container + volume
docker imagesList local images
docker rmi <image>Remove image
docker build -t <tag> .Build image from Dockerfile
docker push <image>Push to registry
docker logs <container>View logs
docker exec -it <container> shShell into container
docker inspect <container>Detailed info

Dockerfile Instructions

InstructionDescriptionExample
FROMBase imageFROM node:alpine
LABELMetadataLABEL author="Dev"
ENVEnvironment variableENV PORT=3000
ARGBuild argumentARG VERSION=1.0
WORKDIRWorking directoryWORKDIR /app
COPYCopy filesCOPY . .
RUNExecute command (creates layer)RUN npm install
EXPOSEDocument portEXPOSE 3000
ENTRYPOINTMain commandENTRYPOINT ["node", "server.js"]
CMDDefault argumentsCMD ["npm", "start"]

Volume Types

TypeSyntaxDescription
Docker-managed-v /container/pathDocker manages host location
Bind mount-v /host/path:/container/pathYou specify host path
Named volume-v vol-name:/container/pathNamed, Docker-managed
Anonymous-v /container/pathUnnamed temporary volume

Network Types

TypeDescriptionUse Case
bridgeDefault isolated networkContainers on same host
hostContainer shares host networkMaximum performance (Linux)
noneNo networkingComplete isolation
overlayMulti-host (Swarm/K8s)Distributed clusters

9. Architecture Diagrams

Development Flow with Volumes

graph LR
    subgraph Host["Host Machine"]
        SRC["Source code\n(working directory)"]
    end

    subgraph Container["Node.js Container"]
        direction TB
        APP["/var/www/app\n(bind mount)"]
        MODS["/var/www/app/node_modules\n(anonymous volume)"]
        SERVER["node server.js\n(running)"]
    end

    DEV["Developer\nedits files"] --> SRC
    SRC -->|"docker run -v $(pwd):/var/www/app"| APP
    APP --> SERVER
    SERVER -->|"Auto-reload\n(nodemon/watch)"| SERVER
    SERVER -->|"localhost:8080"| BROWSER["Browser"]

Multi-stage Build Flow

flowchart LR
    subgraph Stage1["Stage 1: BUILD\n(node:20 or dotnet/sdk)"]
        DEPS["npm install / dotnet restore"]
        COMPILE["npm run build / dotnet publish"]
        ARTIFACTS["Build artifacts\n(/app/dist or /app/publish)"]
        DEPS --> COMPILE --> ARTIFACTS
    end

    subgraph Stage2["Stage 2: PRODUCTION\n(node:20-alpine or dotnet/aspnet)"]
        COPY_ONLY["COPY --from=build\n(artifacts only)"]
        PROD_DEPS["npm ci --only=production"]
        FINAL["Final image\n(lightweight, no SDK)"]
        COPY_ONLY --> PROD_DEPS --> FINAL
    end

    REGISTRY["Docker Hub / ACR / ECR"]
    SRC["Source code"] --> Stage1
    ARTIFACTS -->|"COPY --from=build"| Stage2
    FINAL -->|"docker push"| REGISTRY

Search Terms

docker · web · developers · containerization · containers · kubernetes · dockerfile · volume · commands · container · custom · development · flow · image · volumes · compose · essential · images · multi-stage · network · option · types

Interested in this course?

Contact us to book it or get a custom training plan for your team.