Hands-on LabLesson 13 of 16

🧪 Lab: Build and Run a Containerized Web App

End-to-end hands-on lab — write a Node.js web app, create a production-quality Dockerfile, build the image, run it, and verify it works correctly.

🎯
Lab Objectives

By the end of this lab you will have: written a Dockerfile from scratch, built an image, run a web server container with port mapping, verified it in a browser, and used exec/logs/inspect for diagnostics.

📋 Prerequisites

🔧 Step 1 — Create the Web App

bash
# Create a project folder mkdir docker-lab && cd docker-lab # Initialize Node.js project npm init -y # Install Express npm install express
javascript
# server.js const express = require('express') const app = express() const PORT = process.env.PORT || 3000 app.get('/', (req, res) => { res.json({ message: 'Hello from Docker!', hostname: require('os').hostname(), timestamp: new Date().toISOString() }) }) app.get('/healthz', (req, res) => res.json({ status: 'ok' })) app.listen(PORT, () => console.log(`Server running on port ${PORT}`)) // Graceful shutdown process.on('SIGTERM', () => { console.log('SIGTERM received') process.exit(0) })

🔧 Step 2 — Create Dockerfile and .dockerignore

dockerfile
# Dockerfile FROM node:18-alpine WORKDIR /app # Copy deps first for layer cache COPY package*.json ./ RUN npm ci --only=production # Copy application code COPY server.js . # Non-root user RUN addgroup -S appgroup && adduser -S appuser -G appgroup USER appuser EXPOSE 3000 HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ CMD wget -qO- http://localhost:3000/healthz || exit 1 CMD ["node", "server.js"]
.dockerignore
# .dockerignore node_modules .git .env *.log

🔧 Step 3 — Build and Run

bash
# Build the image docker build -t mywebapp:1.0 . # Verify image was created docker image ls mywebapp # Run the container docker run -d \ --name mywebapp \ -p 8080:3000 \ --memory 256m \ mywebapp:1.0 # Check it is running docker ps # Test in browser or with curl curl http://localhost:8080 # Expected: {"message":"Hello from Docker!","hostname":"...","timestamp":"..."}

🔧 Step 4 — Verify and Inspect

bash
# View logs docker logs mywebapp # Check health status docker inspect --format "{{.State.Health.Status}}" mywebapp # Exec into the running container docker exec -it mywebapp /bin/sh # Inside: ps aux, env, ls -la /app # Type 'exit' to leave # Check resource usage docker stats mywebapp --no-stream # View image layers docker image history mywebapp:1.0

🔧 Step 5 — Cleanup

bash
# Stop and remove container docker stop mywebapp docker rm mywebapp # Remove image docker rmi mywebapp:1.0 # Remove all stopped containers docker container prune

✅ Lab Completion Checklist

💡 Bonus Challenges

  1. Add a /metrics endpoint returning CPU and memory stats.
  2. Use a multi-stage build to further minimize image size.
  3. Add a Redis container on the same network and connect from the web app.
  4. Compare image sizes with node:18 vs node:18-alpine base.