Lesson Goal
Understand the role of operating systems, compare virtual machines and containers, and apply Docker fundamentals to package and run applications consistently.
1) Operating System Fundamentals
The operating system is the software layer that mediates between hardware resources and applications. It manages execution, memory, storage, devices, and security boundaries.
- Processes: independent execution units with their own memory space.
- Threads: lighter execution units inside a process, sharing memory.
- Memory management: allocation, paging, and virtual memory control.
- Scheduling: deciding which process or thread receives CPU time.
2) Virtual Machines vs Containers
| Criterion | Virtual Machine | Container |
|---|---|---|
| Isolation | Full guest operating system | Shared kernel, isolated processes |
| Weight | Heavier (GBs) | Lighter (MBs) |
| Startup | Slower (minutes) | Faster (seconds) |
| Use Case | Strong environment separation | Application packaging and portability |
Containers are ideal when the goal is to package an application consistently. Virtual machines remain useful when full operating system separation is required.
3) Simulating Smaller Machines with Docker
Docker can do more than package and run applications. It can also simulate constrained infrastructure by limiting how much RAM and CPU a container may use. This is useful when the team wants to validate application behavior on smaller machines before deploying to the cloud or reproducing production incidents.
| Flag | Purpose | Example |
|---|---|---|
--memory | Caps available RAM | --memory="512m" |
--cpus | Limits CPU quota, approximating fewer vCPUs | --cpus="0.5" |
mem_limit | Persists memory limits in Compose | mem_limit: 512m |
cpus | Persists CPU limits in Compose | cpus: 0.5 |
docker run --rm --memory="512m" --cpus="0.5" myapp:dev
docker run --rm --memory="1g" --cpus="1.0" -p 8080:80 myapp:staging
services:
app:
image: myapp:dev
mem_limit: 512m
cpus: 0.5These limits help answer practical questions: Does the app still start correctly? Does it become too slow under CPU pressure? Does it consume too much memory and crash? This kind of test is especially useful for validating sizing assumptions, reproducing incidents seen only in smaller instances, and catching performance regressions earlier.
- Memory-constrained tests: reveal out-of-memory crashes, excessive garbage collection, and unstable startup behavior.
- CPU-constrained tests: reveal slow response times, worker starvation, queue buildup, and concurrency bottlenecks.
- Team alignment: Compose-based limits let everyone run the same constrained profile in development, staging, or CI.
4) Docker Hub: Image Types and Trust Levels
Understanding image sources is critical for security and maintainability. Docker Hub provides three tiers of images:
✅ Official Images
Maintained by Docker and verified publishers. Highest trust level with regular security patches and documentation.
docker pull node:20-alpine docker pull postgres:16 docker pull redis:7
✓ Verified Publisher Images
From verified organizations on Docker Hub. Maintained by the software vendors themselves.
docker pull mysql/mysql-server:8.0 docker pull mongo/mongodb-community-server:latest
⚠️ Community Images
Created by users. Use with caution — always review Dockerfile, check stars/downloads, and verify security before production use.
docker pull someuser/custom-app:1.0
5) Multi-Environment Dockerization Strategy
Different environments require different configurations. Using separate Dockerfiles ensures each environment is optimized for its purpose while maintaining consistency.
Environment Comparison
| Aspect | Development | Staging | Production |
|---|---|---|---|
| Base Image | node:20-alpine | node:20-alpine | node:20-alpine (multi-stage) |
| Dependencies | All (dev + prod) | Production only | Production only |
| Source Code | Live editing enabled | Pre-built (dist/) | Pre-built (dist/) |
| Debug Tools | Enabled | Profiling only | Disabled |
| Security | Standard | Standard | Non-root user |
| Image Size | Larger | Medium | Minimal |
6) Development Dockerfile
Optimized for developer productivity with hot reload, debug tools, and verbose logging.
# Dockerfile.dev FROM node:20-alpine WORKDIR /app # Install all dependencies (dev + production) COPY package*.json ./ RUN npm ci # Copy source for live editing COPY . . # Development settings ENV NODE_ENV=development ENV DEBUG=true EXPOSE 3000 # Hot reload with ts-node/nodemon CMD ["npm", "run", "dev"]
Key features: Fast iteration, source code mounted as volume, debug breakpoints enabled, test runners available.
7) Staging Dockerfile
Pre-production validation with realistic constraints. Mirrors production while enabling profiling and integration tests.
# Dockerfile.staging FROM node:20-alpine WORKDIR /app # Production dependencies only COPY package*.json ./ RUN npm ci --only=production # Copy pre-built application COPY dist ./dist # Staging-specific settings ENV NODE_ENV=staging ENV LOG_LEVEL=info ENV ENABLE_PROFILING=true EXPOSE 3000 CMD ["node", "dist/main.js"]
Key features: Production-like behavior, profiling enabled for performance analysis, integration test ready.
8) Production Dockerfile (Multi-Stage)
Optimized for security, minimal size, and performance. Uses multi-stage builds to separate build-time and runtime dependencies.
# Dockerfile.prod
# Stage 1: Build
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Stage 2: Runtime
FROM node:20-alpine
# Non-root user for security
RUN addgroup -g 1001 -S nodejs && \
adduser -S nodejs -u 1001
WORKDIR /app
# Copy only production dependencies and build
COPY package*.json ./
RUN npm ci --only=production && npm cache clean --force
COPY --from=builder /app/dist ./dist
RUN chown -R nodejs:nodejs /app
USER nodejs
# Production settings
ENV NODE_ENV=production
ENV LOG_LEVEL=warn
EXPOSE 3000
CMD ["node", "dist/main.js"]Multi-Stage Benefits
- Smaller image: Build tools (TypeScript compiler, dev dependencies) are not included in the final image
- Security: Non-root user prevents container escape attacks
- Cache efficiency: Layer caching for dependencies speeds up builds
- Minimal attack surface: Only runtime dependencies included
9) Staged Docker Up Workflow
Docker containerization should be treated as a verification process, not just deployment. Each stage validates specific requirements.
Stage 1: Load & Build Verification
# Build and verify image creation docker build -f Dockerfile.dev -t myapp:dev . # Check image layers and size docker history myapp:dev docker images myapp:dev # Verify TypeScript compilation docker run --rm myapp:dev npm run build
✅ Verify: Build succeeds, no TypeScript errors, expected image size, all layers cached properly.
Stage 2: Container Startup Tests
# Run with health check docker run -d --name myapp-test \ -p 3000:3000 \ --health-cmd="curl -f http://localhost:3000/health || exit 1" \ --health-interval=10s \ --health-retries=3 \ myapp:dev # Check container status docker ps docker logs myapp-test # Test health endpoint curl http://localhost:3000/health
✅ Verify: Container starts, health check passes, no crash loops, environment variables loaded correctly.
10) Stage 3: Business Rules & ORM Verification
Infrastructure requirements are business constraints. Database schemas, migrations, and entity mappings must be validated before deployment.
Data Integrity
- Unique constraints (emails, usernames)
- Foreign keys (referential integrity)
- Not null (required fields)
- Data types (correct column types)
Transaction Management
- ACID compliance
- Rollback on failure
- Isolation levels
- Locking strategies
TypeORM / Prisma Infrastructure Checks
# Database connectivity test docker run -d --name postgres-db \ -e POSTGRES_DB=myapp \ -e POSTGRES_USER=admin \ -e POSTGRES_PASSWORD=secret \ -p 5432:5432 \ postgres:16 # Run migration verification (TypeORM) docker run --rm \ --link postgres-db:db \ -e DATABASE_URL=postgresql://admin:secret@db:5432/myapp \ myapp:dev npm run typeorm migration:run # Verify entity mappings (Prisma) docker run --rm \ -e DATABASE_URL=postgresql://admin:secret@db:5432/myapp \ myapp:dev npx prisma db pull # Validate schema sync docker run --rm \ -e DATABASE_URL=postgresql://admin:secret@db:5432/myapp \ myapp:dev npm run schema:validate # Run integration tests docker run --rm \ --link postgres-db:db \ -e DATABASE_URL=postgresql://admin:secret@db:5432/myapp \ myapp:dev npm run test:integration
✅ Verify: Database connection, migrations execute successfully, entity mappings correct, relationship constraints enforced, integration tests pass.
11) Stage 4: Full Stack Verification
End-to-end testing in a fully containerized environment that mirrors production architecture.
# docker-compose.test.yml
version: '3.8'
services:
db:
image: postgres:16
environment:
POSTGRES_DB: myapp
POSTGRES_USER: admin
POSTGRES_PASSWORD: secret
healthcheck:
test: ["CMD-SHELL", "pg_isready -U admin"]
interval: 10s
retries: 5
app:
build:
context: .
dockerfile: Dockerfile.dev
ports:
- "3000:3000"
environment:
DATABASE_URL: postgresql://admin:secret@db:5432/myapp
depends_on:
db:
condition: service_healthy
test:
build:
context: .
dockerfile: Dockerfile.dev
command: npm run test:e2e
environment:
DATABASE_URL: postgresql://admin:secret@db:5432/myapp
depends_on:
- dbRun command:
docker-compose -f docker-compose.test.yml up --abort-on-container-exit
✅ Verify: All services start correctly, database health checks pass, application connects to database, end-to-end tests execute successfully.
12) Docker as Executable Documentation
Docker is not just a deployment tool — it's living documentation of infrastructure requirements and business constraints.
Requirement Validation
Dockerfile exposes dependencies early, forcing teams to document and validate infrastructure needs before coding begins.
Environment Consistency
dev = staging = production. Eliminates "works on my machine" problems and reduces environment-specific bugs.
Onboarding
New developers run docker-compose up and have a working environment in minutes, not days.
CI/CD Readiness
Same containers used in development run in CI/CD pipelines, ensuring test validity.
13) Requirements Traceability Matrix
How Docker stages map to business and technical requirements:
| Requirement Type | Docker Stage | Verification Method |
|---|---|---|
| Build Requirements | Stage 1 | TypeScript compilation, dependency installation |
| Runtime Requirements | Stage 2 | Health checks, container stability |
| Data Integrity | Stage 3 | Migration execution, schema validation |
| Business Rules | Stage 3 | ORM entity mappings, constraint enforcement |
| Integration | Stage 4 | End-to-end tests, service communication |
| Security | All Stages | Non-root user, minimal dependencies, secrets management |
14) Best Practices Summary
- Use official images: Start with
node:20-alpineor other verified base images - Multi-stage builds: Separate build and runtime environments for production
- Non-root users: Always run production containers as non-root
- .dockerignore: Exclude
node_modules,.git,.env, and build artifacts - Layer caching: Copy
package*.jsonbefore source code - Health checks: Define health endpoints and monitor container health
- Environment variables: Never hardcode secrets; use environment variables or secrets managers
- Staged verification: Build → Startup → Business Rules → Full Stack