Home / Developer Tools / Docker for Developers: Containerizing Your Applications
Developer Tools

Docker for Developers: Containerizing Your Applications

Docker transforms the 'works on my machine' problem into a solved equation. When developers spend hours debugging environment inconsistencies—Node version...

What you will learn

Practical execution with concise explanations, real implementation patterns, and production-ready recommendations.


### 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

Terminal output for docker build

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

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) or distroless for production
  • Multi-stage builds: separate build dependencies from runtime
  • Pin exact versions: node:20.11.0-alpine not node:latest
  • Order layers by change frequency: dependencies before source code
  • Use .dockerignore: exclude node_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-alpine regularly

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

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

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.json before source code
  • VS Code Dev Containers provide consistent team development environments
  • Volume strategies enable hot reload for instant feedback during development
  • BuildKit and .dockerignore dramatically improve build performance

Next Steps

  • Registry Setup: Push images to GitHub Container Registry or Azure Container Registry
  • CI/CD Integration: Add docker build and docker push to 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