### Step 2: Build & Run Locally
```bash
docker build -t myapp:dev .
docker run --rm -p 3000:3000 myapp:dev
Expected output:
[+] Building 24.3s (12/12) FINISHED
=> naming to docker.io/library/myapp:latest
Step 3: Add Multi-Stage Build (Optimization)
FROM node:20-alpine AS build
WORKDIR /src
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:20-alpine AS runtime
WORKDIR /app
COPY --from=build /src/dist ./dist
CMD ["node", "dist/index.js"]
Step 4: Debugging Containers with VS Code
Interactive Shell Debugging:
# Exec into running container
docker exec -it myapp-container sh
# View real-time logs
docker logs -f myapp-container
# Inspect container configuration
docker inspect myapp-container
VS Code Dev Containers (.devcontainer/devcontainer.json):
{
"name": "MyApp Dev",
"dockerFile": "../Dockerfile.dev",
"forwardPorts": [3000, 5432],
"postCreateCommand": "npm install",
"customizations": {
"vscode": {
"extensions": [
"dbaeumer.vscode-eslint",
"ms-azuretools.vscode-docker"
]
}
}
}
Attach Debugger to Running Node.js Container:
# Dockerfile.dev - expose debug port
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 9229
CMD ["node", "--inspect=0.0.0.0:9229", "src/index.js"]
# Run with debug port exposed
docker run -p 3000:3000 -p 9229:9229 myapp:dev
VS Code launch.json:
{
"type": "node",
"request": "attach",
"name": "Docker: Attach to Node",
"port": 9229,
"restart": true,
"remoteRoot": "/app"
}
Step 5: Compose Multi-Service Dev Env
version: "3.9"
services:
api:
```yaml
build: .
ports: ["5000:5000"]```
redis:
```yaml
image: redis:7-alpine
ports: ["6379:6379"]
## Docker Networking for Microservices
**Bridge Network (Default):**
```bash
# Create custom network
docker network create myapp-network
# Run containers in same network
docker run --network myapp-network --name api myapp:latest
docker run --network myapp-network --name db postgres:15
# Services communicate via container names
# API connects to postgresql://db:5432
Docker Compose Networking (Automatic):
version: "3.9"
services:
api:
build: .
ports: ["3000:3000"]
depends_on:
- db
- redis
db:
image: postgres:15
environment:
POSTGRES_PASSWORD: dev123
redis:
image: redis:7-alpine
# Compose creates default network automatically
# api can reach db:5432 and redis:6379
Volume Strategies for Development
Figure: Server Manager Storage Pools – disk allocation and volume wizard.
Bind Mounts for Hot Reload:
services:
api:
build: .
volumes:
- ./src:/app/src # Live code changes
- /app/node_modules # Prevent overwriting
command: npm run dev # Watch mode
Named Volumes for Data Persistence:
services:
db:
image: postgres:15
volumes:
- pgdata:/var/lib/postgresql/data # Persists across rebuilds
volumes:
pgdata:
Best Practices
Image Optimization:
- Use smallest base image:
alpine(5MB) ordistrolessfor production - Multi-stage builds: separate build dependencies from runtime
- Pin exact versions:
node:20.11.0-alpinenotnode:latest - Order layers by change frequency: dependencies before source code
- Use
.dockerignore: excludenode_modules,.git, tests, docs
Security:
- Never run containers as root:
USER appuser - Scan images:
docker scan myapp:latest - Use secrets management: Docker Secrets or environment variables, never hardcode
- Keep base images updated:
docker pull node:20-alpineregularly
Development Workflow:
- Dev containers (VS Code): consistent team environments
- Hot reload with bind mounts: instant feedback
- Separate Dockerfile.dev: include debugging tools, skip optimizations
- Use Docker Compose: orchestrate entire stack locally
Build Performance:
- Enable BuildKit:
DOCKER_BUILDKIT=1 docker build . - Layer caching: copy dependency files before source code
.dockerignore: reduce build context size- Parallel builds:
docker compose build --parallel
Common Issues & Troubleshooting
Figure: Configuration and management dashboard with status overview.
Issue: Large image size (500MB+ for simple apps)
# Problem: Including dev dependencies and build artifacts
FROM node:20
COPY . .
RUN npm install # Includes devDependencies!
# Solution 1: Multi-stage build
FROM node:20 AS build
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:20-alpine # 40MB vs 1GB
COPY --from=build /app/dist ./dist
COPY package*.json ./
RUN npm ci --only=production
CMD ["node", "dist/index.js"]
# Solution 2: .dockerignore
node_modules
.git
*.md
tests/
.env.local
Issue: Slow builds (rebuilding unchanged layers)
# Problem: Copy everything first, invalidates cache
COPY . .
RUN npm install
# Solution: Copy dependency files first
COPY package*.json ./
RUN npm ci # Cached unless package.json changes
COPY . . # Source changes don't invalidate npm install
Issue: Container can't connect to other services
# Problem: Services in separate networks
docker run --name api myapp:latest
docker run --name db postgres:15
# API can't reach "db" hostname
# Solution: Use Docker Compose networking
version: "3.9"
services:
api:
build: .
environment:
- DATABASE_URL=postgresql://db:5432/mydb
db:
image: postgres:15
# Services automatically share network, "db" resolves
Issue: Volume permission errors (Linux)
# Problem: Container writes as root, host can't read files
volumes:
- ./data:/app/data
# Solution: Match user ID in Dockerfile
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser
Issue: Environment variables not loading
# Problem: .env file not passed to container
docker run myapp:latest # Can't read .env
# Solution 1: docker-compose.yml
services:
api:
env_file: .env
# Solution 2: Explicit --env-file
docker run --env-file .env myapp:latest
Issue: Container crashes immediately
# Debugging steps
docker logs <container-id> # Check application logs
docker inspect <container-id> | grep -A 5 "State" # Exit code
docker run -it myapp:latest sh # Interactive shell
# Common causes:
# - Missing CMD/ENTRYPOINT
# - Application crash at startup
# - Health check failing too quickly
Real-World Developer Impact
Figure: Configuration and management dashboard with status overview.
Case Study: Onboarding Acceleration
- Before Docker: 2-3 days installing dependencies, configuring databases, fixing version conflicts
- After Docker: 30 minutes running
docker compose up, including database seeding - Impact: New developers productive on day one
Case Study: CI/CD Reliability
- Before: "Works in CI but not production" due to environment drift
- After: Same container image from dev → test → production
- Impact: 80% reduction in environment-related bugs
Case Study: Multi-Service Testing
- Before: Mock external services, limited integration testing
- After: Full stack (API, database, Redis, message queue) running locally
- Impact: Integration bugs caught before code review
Architecture Decision and Tradeoffs
When designing development workflow solutions with Developer Tools, consider these key architectural trade-offs:
| Approach | Best For | Tradeoff |
|---|---|---|
| Managed / platform service | Rapid delivery, reduced ops burden | Less customisation, potential vendor lock-in |
| Custom / self-hosted | Full control, advanced tuning | Higher operational overhead and cost |
Recommendation: Start with the managed approach for most workloads and move to custom only when specific requirements demand it.
Validation and Versioning
- Last validated: April 2026
- Validate examples against your tenant, region, and SKU constraints before production rollout.
- Keep module, CLI, and SDK versions pinned in automation pipelines and review quarterly.
Security and Governance Considerations
- Apply least-privilege access using RBAC roles and just-in-time elevation for admin tasks.
- Store secrets in managed secret stores and avoid embedding credentials in scripts or source files.
- Enable audit logging, data protection policies, and periodic access reviews for regulated workloads.
Cost and Performance Notes
- Define budgets and alerts, then monitor usage and cost trends continuously after go-live.
- Baseline performance with synthetic and real-user checks before and after major changes.
- Scale resources with measured thresholds and revisit sizing after usage pattern changes.
Official Microsoft References
- https://learn.microsoft.com/visualstudio/
- https://learn.microsoft.com/azure/devops/
- https://learn.microsoft.com/github/
Public Examples from Official Sources
- These examples are sourced from official public Microsoft documentation and sample repositories.
- Documentation examples: https://learn.microsoft.com/visualstudio/
- Sample repositories: https://github.com/microsoft/vscode-extension-samples
- Prefer adapting these examples to your tenant, subscriptions, and governance requirements before production use.
Key Takeaways
- Docker eliminates "works on my machine" by packaging applications with exact dependencies
- Multi-stage builds produce lean images (40MB Alpine vs 1GB full Node.js)
- Docker Compose orchestrates multi-service stacks with automatic networking
- Layer caching accelerates builds: copy
package.jsonbefore source code - VS Code Dev Containers provide consistent team development environments
- Volume strategies enable hot reload for instant feedback during development
- BuildKit and
.dockerignoredramatically improve build performance
Next Steps
- Registry Setup: Push images to GitHub Container Registry or Azure Container Registry
- CI/CD Integration: Add
docker buildanddocker pushto GitHub Actions - Image Scanning: Integrate Trivy or Snyk for vulnerability scanning
- Kubernetes Prep: Learn pod specs, deployments, services for orchestration
- Advanced Patterns: Explore distroless images, multi-architecture builds (ARM + x64)
- Production Hardening: Add health checks, resource limits, readiness probes
- Monitoring: Instrument containers with Prometheus metrics, structured logging
Additional Resources
What was your biggest win after adopting Docker?
Discussion