Tools: ๐Ÿณ How to Run Any Project in Docker: A Complete Guide (2026)

Tools: ๐Ÿณ How to Run Any Project in Docker: A Complete Guide (2026)

Why Docker?

Prerequisites

Part 1: The Anatomy of a Dockerfile

Key Instructions Explained

Part 2: Dockerizing a Node.js Project

Project structure

Dockerfile

Build and run

Part 3: Dockerizing a Python Project

Part 4: Docker Compose โ€” Running Multiple Services

Example: Node.js app + PostgreSQL + Redis

Run everything with one command

Part 5: Environment Variables & Secrets

Part 6: Development vs Production Configurations

Part 7: Useful Docker Commands Cheat Sheet

Images

Containers

Debugging

Part 8: The .dockerignore File

Part 9: Multi-Stage Builds (Advanced)

Common Pitfalls & How to Avoid Them

โŒ App can't connect to the database

โŒ Changes not reflected after rebuild

โŒ Container exits immediately

โŒ Port already in use

Wrapping Up

What's Next? From zero to containerized in minutes โ€” no "works on my machine" excuses You've probably heard it before: "It works on my machine." Docker exists to make that phrase obsolete. Docker lets you package your application and all its dependencies โ€” runtimes, libraries, config files โ€” into a single, portable unit called a container. That container runs identically on your laptop, your teammate's Windows machine, a CI server, or a cloud VM. Before we dive in, here's the quick mental model: A Dockerfile is a plain text file with instructions Docker reads top-to-bottom to build your image. Pro tip: Order your Dockerfile from least-to-most frequently changed. Docker caches each layer, so stable layers (like installing dependencies) won't re-run unless they change. Visit http://localhost:8080 โ€” your app is running inside Docker. Note the --host 0.0.0.0: By default, many dev servers bind to 127.0.0.1 (localhost inside the container). You must bind to 0.0.0.0 to accept connections from outside the container. Real projects rarely have just one service. You need a database, a cache, maybe a background worker. Docker Compose lets you define and run all of them together. Never hardcode secrets in your Dockerfile or Compose file. Use a .env file: Docker Compose automatically picks up .env in the same directory: For production, use Docker Secrets, Vault, AWS Secrets Manager, or your platform's secret management. Use multiple Compose files to separate concerns: docker-compose.dev.yml โ€” adds hot reload: docker-compose.prod.yml โ€” tightens things up: Run with merged configs: Just like .gitignore, .dockerignore prevents files from being copied into your image. This keeps images small and builds fast. Without this, COPY . . would copy your entire node_modules (hundreds of MB) into the image โ€” even though you're running npm install inside it anyway. Multi-stage builds let you use a heavy build image and copy only the artifacts into a lean production image. The final image contains no TypeScript compiler, test libraries, or source files โ€” just what's needed to run. This can shrink image size from 1GB+ โ†’ under 200MB. Inside a Docker network, containers talk to each other by service name, not localhost. Docker caches layers. Force a full rebuild: The most common cause: your CMD is wrong, or the process crashes on startup. Either stop the conflicting service or change the host port mapping: Here's what you've learned: Docker has a learning curve, but once it clicks, you'll never want to go back to "just run it locally". Your entire team gets identical environments, onboarding new developers takes minutes instead of hours, and deployments become deterministic. Found this helpful? Drop a โค๏ธ and follow for more DevOps and backend content. Got questions? Ask in the comments โ€” I read every one. Templates let you quickly answer FAQs or store snippets for re-use. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse

Command

Copy

$ -weight: 500;">docker --version # Docker version 26.x.x -weight: 500;">docker compose version # Docker Compose version v2.x.x -weight: 500;">docker --version # Docker version 26.x.x -weight: 500;">docker compose version # Docker Compose version v2.x.x -weight: 500;">docker --version # Docker version 26.x.x -weight: 500;">docker compose version # Docker Compose version v2.x.x # 1. Base image โ€” what you're building ON TOP OF FROM node:20-alpine # 2. Set the working directory inside the container WORKDIR /app # 3. Copy dependency files first (for layer caching) COPY package*.json ./ # 4. Install dependencies RUN -weight: 500;">npm -weight: 500;">install # 5. Copy the rest of your source code COPY . . # 6. Expose the port your app listens on EXPOSE 3000 # 7. The command to run when the container starts CMD ["node", "server.js"] # 1. Base image โ€” what you're building ON TOP OF FROM node:20-alpine # 2. Set the working directory inside the container WORKDIR /app # 3. Copy dependency files first (for layer caching) COPY package*.json ./ # 4. Install dependencies RUN -weight: 500;">npm -weight: 500;">install # 5. Copy the rest of your source code COPY . . # 6. Expose the port your app listens on EXPOSE 3000 # 7. The command to run when the container starts CMD ["node", "server.js"] # 1. Base image โ€” what you're building ON TOP OF FROM node:20-alpine # 2. Set the working directory inside the container WORKDIR /app # 3. Copy dependency files first (for layer caching) COPY package*.json ./ # 4. Install dependencies RUN -weight: 500;">npm -weight: 500;">install # 5. Copy the rest of your source code COPY . . # 6. Expose the port your app listens on EXPOSE 3000 # 7. The command to run when the container starts CMD ["node", "server.js"] my-app/ โ”œโ”€โ”€ src/ โ”‚ โ””โ”€โ”€ index.js โ”œโ”€โ”€ package.json โ”œโ”€โ”€ package-lock.json โ””โ”€โ”€ Dockerfile my-app/ โ”œโ”€โ”€ src/ โ”‚ โ””โ”€โ”€ index.js โ”œโ”€โ”€ package.json โ”œโ”€โ”€ package-lock.json โ””โ”€โ”€ Dockerfile my-app/ โ”œโ”€โ”€ src/ โ”‚ โ””โ”€โ”€ index.js โ”œโ”€โ”€ package.json โ”œโ”€โ”€ package-lock.json โ””โ”€โ”€ Dockerfile FROM node:20-alpine WORKDIR /app # Copy lockfile and package.json first for cache efficiency COPY package*.json ./ RUN -weight: 500;">npm ci --only=production COPY src/ ./src/ EXPOSE 3000 CMD ["node", "src/index.js"] FROM node:20-alpine WORKDIR /app # Copy lockfile and package.json first for cache efficiency COPY package*.json ./ RUN -weight: 500;">npm ci --only=production COPY src/ ./src/ EXPOSE 3000 CMD ["node", "src/index.js"] FROM node:20-alpine WORKDIR /app # Copy lockfile and package.json first for cache efficiency COPY package*.json ./ RUN -weight: 500;">npm ci --only=production COPY src/ ./src/ EXPOSE 3000 CMD ["node", "src/index.js"] # Build the image and tag it -weight: 500;">docker build -t my-node-app . # Run it, mapping host port 8080 โ†’ container port 3000 -weight: 500;">docker run -p 8080:3000 my-node-app # Build the image and tag it -weight: 500;">docker build -t my-node-app . # Run it, mapping host port 8080 โ†’ container port 3000 -weight: 500;">docker run -p 8080:3000 my-node-app # Build the image and tag it -weight: 500;">docker build -t my-node-app . # Run it, mapping host port 8080 โ†’ container port 3000 -weight: 500;">docker run -p 8080:3000 my-node-app FROM python:3.12-slim WORKDIR /app # Install dependencies COPY requirements.txt . RUN -weight: 500;">pip -weight: 500;">install --no-cache-dir -r requirements.txt COPY . . EXPOSE 8000 CMD ["python", "-m", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"] FROM python:3.12-slim WORKDIR /app # Install dependencies COPY requirements.txt . RUN -weight: 500;">pip -weight: 500;">install --no-cache-dir -r requirements.txt COPY . . EXPOSE 8000 CMD ["python", "-m", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"] FROM python:3.12-slim WORKDIR /app # Install dependencies COPY requirements.txt . RUN -weight: 500;">pip -weight: 500;">install --no-cache-dir -r requirements.txt COPY . . EXPOSE 8000 CMD ["python", "-m", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"] # -weight: 500;">docker-compose.yml services: app: build: . ports: - "3000:3000" environment: - DATABASE_URL=postgres://user:password@db:5432/mydb - REDIS_URL=redis://cache:6379 depends_on: db: condition: service_healthy cache: condition: service_started volumes: - .:/app # Mount source code for hot reload - /app/node_modules # Prevent host node_modules from overwriting db: image: postgres:16-alpine environment: POSTGRES_USER: user POSTGRES_PASSWORD: password POSTGRES_DB: mydb volumes: - postgres_data:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U user -d mydb"] interval: 5s timeout: 5s retries: 5 cache: image: redis:7-alpine ports: - "6379:6379" volumes: postgres_data: # -weight: 500;">docker-compose.yml services: app: build: . ports: - "3000:3000" environment: - DATABASE_URL=postgres://user:password@db:5432/mydb - REDIS_URL=redis://cache:6379 depends_on: db: condition: service_healthy cache: condition: service_started volumes: - .:/app # Mount source code for hot reload - /app/node_modules # Prevent host node_modules from overwriting db: image: postgres:16-alpine environment: POSTGRES_USER: user POSTGRES_PASSWORD: password POSTGRES_DB: mydb volumes: - postgres_data:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U user -d mydb"] interval: 5s timeout: 5s retries: 5 cache: image: redis:7-alpine ports: - "6379:6379" volumes: postgres_data: # -weight: 500;">docker-compose.yml services: app: build: . ports: - "3000:3000" environment: - DATABASE_URL=postgres://user:password@db:5432/mydb - REDIS_URL=redis://cache:6379 depends_on: db: condition: service_healthy cache: condition: service_started volumes: - .:/app # Mount source code for hot reload - /app/node_modules # Prevent host node_modules from overwriting db: image: postgres:16-alpine environment: POSTGRES_USER: user POSTGRES_PASSWORD: password POSTGRES_DB: mydb volumes: - postgres_data:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U user -d mydb"] interval: 5s timeout: 5s retries: 5 cache: image: redis:7-alpine ports: - "6379:6379" volumes: postgres_data: # Start all services in the background -weight: 500;">docker compose up -d # View logs -weight: 500;">docker compose logs -f app # Stop everything -weight: 500;">docker compose down # Stop and -weight: 500;">remove volumes (wipes database data) -weight: 500;">docker compose down -v # Start all services in the background -weight: 500;">docker compose up -d # View logs -weight: 500;">docker compose logs -f app # Stop everything -weight: 500;">docker compose down # Stop and -weight: 500;">remove volumes (wipes database data) -weight: 500;">docker compose down -v # Start all services in the background -weight: 500;">docker compose up -d # View logs -weight: 500;">docker compose logs -f app # Stop everything -weight: 500;">docker compose down # Stop and -weight: 500;">remove volumes (wipes database data) -weight: 500;">docker compose down -v # .env (add this to .gitignore!) POSTGRES_PASSWORD=supersecret API_KEY=abc123 # .env (add this to .gitignore!) POSTGRES_PASSWORD=supersecret API_KEY=abc123 # .env (add this to .gitignore!) POSTGRES_PASSWORD=supersecret API_KEY=abc123 services: app: environment: - API_KEY=${API_KEY} db: environment: - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} services: app: environment: - API_KEY=${API_KEY} db: environment: - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} services: app: environment: - API_KEY=${API_KEY} db: environment: - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} my-app/ โ”œโ”€โ”€ -weight: 500;">docker-compose.yml # Base config โ”œโ”€โ”€ -weight: 500;">docker-compose.dev.yml # Dev overrides (hot reload, debug ports) โ””โ”€โ”€ -weight: 500;">docker-compose.prod.yml # Prod overrides (replicas, logging) my-app/ โ”œโ”€โ”€ -weight: 500;">docker-compose.yml # Base config โ”œโ”€โ”€ -weight: 500;">docker-compose.dev.yml # Dev overrides (hot reload, debug ports) โ””โ”€โ”€ -weight: 500;">docker-compose.prod.yml # Prod overrides (replicas, logging) my-app/ โ”œโ”€โ”€ -weight: 500;">docker-compose.yml # Base config โ”œโ”€โ”€ -weight: 500;">docker-compose.dev.yml # Dev overrides (hot reload, debug ports) โ””โ”€โ”€ -weight: 500;">docker-compose.prod.yml # Prod overrides (replicas, logging) services: app: volumes: - .:/app command: -weight: 500;">npm run dev environment: - NODE_ENV=development services: app: volumes: - .:/app command: -weight: 500;">npm run dev environment: - NODE_ENV=development services: app: volumes: - .:/app command: -weight: 500;">npm run dev environment: - NODE_ENV=development services: app: -weight: 500;">restart: always environment: - NODE_ENV=production deploy: replicas: 2 services: app: -weight: 500;">restart: always environment: - NODE_ENV=production deploy: replicas: 2 services: app: -weight: 500;">restart: always environment: - NODE_ENV=production deploy: replicas: 2 # Development -weight: 500;">docker compose -f -weight: 500;">docker-compose.yml -f -weight: 500;">docker-compose.dev.yml up # Production -weight: 500;">docker compose -f -weight: 500;">docker-compose.yml -f -weight: 500;">docker-compose.prod.yml up -d # Development -weight: 500;">docker compose -f -weight: 500;">docker-compose.yml -f -weight: 500;">docker-compose.dev.yml up # Production -weight: 500;">docker compose -f -weight: 500;">docker-compose.yml -f -weight: 500;">docker-compose.prod.yml up -d # Development -weight: 500;">docker compose -f -weight: 500;">docker-compose.yml -f -weight: 500;">docker-compose.dev.yml up # Production -weight: 500;">docker compose -f -weight: 500;">docker-compose.yml -f -weight: 500;">docker-compose.prod.yml up -d -weight: 500;">docker images # List all local images -weight: 500;">docker pull nginx:alpine # Pull image from Docker Hub -weight: 500;">docker rmi my-app # Remove an image -weight: 500;">docker image prune # Remove unused images -weight: 500;">docker images # List all local images -weight: 500;">docker pull nginx:alpine # Pull image from Docker Hub -weight: 500;">docker rmi my-app # Remove an image -weight: 500;">docker image prune # Remove unused images -weight: 500;">docker images # List all local images -weight: 500;">docker pull nginx:alpine # Pull image from Docker Hub -weight: 500;">docker rmi my-app # Remove an image -weight: 500;">docker image prune # Remove unused images -weight: 500;">docker ps # List running containers -weight: 500;">docker ps -a # List all containers (including stopped) -weight: 500;">docker -weight: 500;">stop <container_id> # Gracefully -weight: 500;">stop a container -weight: 500;">docker rm <container_id> # Remove a stopped container -weight: 500;">docker logs -f <container_id> # Tail logs from a container -weight: 500;">docker exec -it <id> sh # Open a shell inside a running container -weight: 500;">docker ps # List running containers -weight: 500;">docker ps -a # List all containers (including stopped) -weight: 500;">docker -weight: 500;">stop <container_id> # Gracefully -weight: 500;">stop a container -weight: 500;">docker rm <container_id> # Remove a stopped container -weight: 500;">docker logs -f <container_id> # Tail logs from a container -weight: 500;">docker exec -it <id> sh # Open a shell inside a running container -weight: 500;">docker ps # List running containers -weight: 500;">docker ps -a # List all containers (including stopped) -weight: 500;">docker -weight: 500;">stop <container_id> # Gracefully -weight: 500;">stop a container -weight: 500;">docker rm <container_id> # Remove a stopped container -weight: 500;">docker logs -f <container_id> # Tail logs from a container -weight: 500;">docker exec -it <id> sh # Open a shell inside a running container # Open an interactive shell in a running container -weight: 500;">docker exec -it my-app-container sh # Run a one-off command -weight: 500;">docker run --rm -it node:20-alpine node --version # Inspect a container's config, network, volumes -weight: 500;">docker inspect <container_id> # Check resource usage -weight: 500;">docker stats # Open an interactive shell in a running container -weight: 500;">docker exec -it my-app-container sh # Run a one-off command -weight: 500;">docker run --rm -it node:20-alpine node --version # Inspect a container's config, network, volumes -weight: 500;">docker inspect <container_id> # Check resource usage -weight: 500;">docker stats # Open an interactive shell in a running container -weight: 500;">docker exec -it my-app-container sh # Run a one-off command -weight: 500;">docker run --rm -it node:20-alpine node --version # Inspect a container's config, network, volumes -weight: 500;">docker inspect <container_id> # Check resource usage -weight: 500;">docker stats node_modules .-weight: 500;">git .env *.log dist coverage .DS_Store README.md -weight: 500;">docker-compose*.yml node_modules .-weight: 500;">git .env *.log dist coverage .DS_Store README.md -weight: 500;">docker-compose*.yml node_modules .-weight: 500;">git .env *.log dist coverage .DS_Store README.md -weight: 500;">docker-compose*.yml # Stage 1: Build FROM node:20-alpine AS builder WORKDIR /app COPY package*.json ./ RUN -weight: 500;">npm ci COPY . . RUN -weight: 500;">npm run build # Produces /app/dist # Stage 2: Production FROM node:20-alpine AS production WORKDIR /app COPY package*.json ./ RUN -weight: 500;">npm ci --only=production COPY --from=builder /app/dist ./dist # Only copy built output EXPOSE 3000 CMD ["node", "dist/server.js"] # Stage 1: Build FROM node:20-alpine AS builder WORKDIR /app COPY package*.json ./ RUN -weight: 500;">npm ci COPY . . RUN -weight: 500;">npm run build # Produces /app/dist # Stage 2: Production FROM node:20-alpine AS production WORKDIR /app COPY package*.json ./ RUN -weight: 500;">npm ci --only=production COPY --from=builder /app/dist ./dist # Only copy built output EXPOSE 3000 CMD ["node", "dist/server.js"] # Stage 1: Build FROM node:20-alpine AS builder WORKDIR /app COPY package*.json ./ RUN -weight: 500;">npm ci COPY . . RUN -weight: 500;">npm run build # Produces /app/dist # Stage 2: Production FROM node:20-alpine AS production WORKDIR /app COPY package*.json ./ RUN -weight: 500;">npm ci --only=production COPY --from=builder /app/dist ./dist # Only copy built output EXPOSE 3000 CMD ["node", "dist/server.js"] // โŒ Wrong const db = new Client({ host: 'localhost' }) // โœ… Correct (use the Compose -weight: 500;">service name) const db = new Client({ host: 'db' }) // โŒ Wrong const db = new Client({ host: 'localhost' }) // โœ… Correct (use the Compose -weight: 500;">service name) const db = new Client({ host: 'db' }) // โŒ Wrong const db = new Client({ host: 'localhost' }) // โœ… Correct (use the Compose -weight: 500;">service name) const db = new Client({ host: 'db' }) -weight: 500;">docker compose build --no-cache -weight: 500;">docker compose build --no-cache -weight: 500;">docker compose build --no-cache -weight: 500;">docker logs <container_id> -weight: 500;">docker logs <container_id> -weight: 500;">docker logs <container_id> ports: - "3001:3000" # Map to 3001 on host instead ports: - "3001:3000" # Map to 3001 on host instead ports: - "3001:3000" # Map to 3001 on host instead - Image โ†’ A blueprint (like a class in OOP) - Container โ†’ A running instance of an image (like an object) - Dockerfile โ†’ The recipe for building an image - Docker Compose โ†’ A tool to orchestrate multiple containers together - Docker Desktop installed (includes Docker Compose) - Basic terminal familiarity - A project to containerize (we'll use examples for Node.js, Python, and a generic approach) - Dockerfile basics โ€” FROM, COPY, RUN, CMD and layer caching - Building & running individual containers with -weight: 500;">docker build / -weight: 500;">docker run - Docker Compose for multi--weight: 500;">service setups (app + database + cache) - Environment variables and keeping secrets out of your images - Dev/prod split using multiple Compose files - Multi-stage builds for lean production images - Debugging techniques when things go sideways - Docker volumes โ€” persisting data beyond the container lifecycle - Docker networks โ€” custom networking between containers - Kubernetes โ€” orchestrating containers at scale - GitHub Actions + Docker โ€” CI/CD pipelines that build and push images automatically