4 October 2025

Containerise an app with Docker

Here’s a practical, step-by-step guide to containerizing an app with Docker. I’ll show a simple example for a Node.js app, but the same workflow applies to many languages (Python, Go, etc.) with small tweaks to the Dockerfile.

High-level steps

  • Install Docker on your machine
  • Prepare your app sources
  • Create a .dockerignore to keep the image small
  • Write a Dockerfile (one or more stages)
  • Build the Docker image
  • Run a container from the image (and test locally)
  • Iterate (add environment, volumes, or multi-stage builds)
  • Push to a registry (optional)

Step-by-step

  1. Install Docker
  • Windows/macOS: install Docker Desktop from Docker’s website and start it.
  • Linux: follow distribution-specific instructions (often docker-ce package and adding your user to the docker group).
  • Verify: run docker version and docker info.
  1. Prepare your app
  • Ensure your app has a start script or entry point.
  • Example for Node.js:
    • package.json with a start script, e.g. “start”: “node index.js”
    • index.js (or app.js) to start your server (e.g., Express).
  1. Create a .dockerignore
  • This keeps the image small by excluding files not needed at runtime.
  • Create a file named .dockerignore with content like:
    • node_modules
    • npm-debug.log
    • .git
    • dist or build (if you don’t need prebuilt artifacts)
    • .DS_Store
    • coverage
  • This helps lazy-install dependencies and avoid shipping dev files.
  1. Write a Dockerfile
  • Create a file named Dockerfile in the project root.
  • Example for a Node.js app (multi-line for clarity):
    • If you’re using a single-stage build (works for small apps):
      • FROM node:18-alpine
      • WORKDIR /app
      • COPY package*.json ./
      • RUN npm install –production
      • COPY . .
      • EXPOSE 3000
      • CMD [“npm”, “start”]
    • For a more robust, production-ready approach (multi-stage to reduce image size):
      • Stage 1: builder
        • FROM node:18-alpine AS builder
        • WORKDIR /app
        • COPY package*.json ./
        • RUN npm install
        • COPY . .
        • RUN npm run build (if you have a build step)
      • Stage 2: runtime
        • FROM node:18-alpine
        • WORKDIR /app
        • COPY –from=builder /app /app
        • RUN npm prune –production
        • EXPOSE 3000
        • CMD [“node”, “index.js”]
  • Key points:
    • Use a small base image (e.g., node:18-alpine) to reduce size.
    • Copy package.json first to leverage Docker cache for dependencies.
    • If you have environment-specific config, consider environment variables with placeholders.
  1. Build the Docker image
  • Run in the project root (where Dockerfile is):
    • docker build -t myapp:1.0 .
    • The tag myapp:1.0 names the image; you can use any name/version.
  1. Run the container locally
  • Basic run (map port 3000 to host 3000):
    • docker run -p 3000:3000 --name myapp-container myapp:1.0
  • If your app needs env vars:
    • docker run -p 3000:3000 -e NODE_ENV=production --name myapp-container myapp:1.0
  • If you want to keep logs/data outside container (volumes):
    • docker run -p 3000:3000 -v $(pwd)/data:/app/data --name myapp-container myapp:1.0
  1. Test your app
  • Open http://localhost:3000 (or whatever port you exposed) in a browser or use curl.
  • Check logs with:
    • docker logs myapp-container
  • Attach to a running container (for interactive debugging):
    • docker attach myapp-container (or docker logs -f for live logs)
  1. Iterate and improve
  • Development workflow: use a volume for code changes to reflect without rebuilding:
    • docker run -p 3000:3000 -v "$PWD":/app -w /app --name myapp-container myapp:1.0
  • Production considerations:
    • Use a multi-stage Dockerfile to minimize image size.
    • Use a non-root user inside the container.
    • Pin exact base image versions to avoid surprises.
    • Add HEALTHCHECK in Dockerfile to monitor app health.
  1. Optional: use Docker Compose for multi-container apps
  • If your app depends on services (e.g., a database), create a docker-compose.yml:
    • version: “3.9”
    • services:
      • web: build: . ports: – “3000:3000” environment: – NODE_ENV=production depends_on: – db
      • db: image: postgres:15-alpine environment: – POSTGRES_USER=user – POSTGRES_PASSWORD=pass volumes: – db-data:/var/lib/postgresql/data
    • volumes:
      • db-data:
  • Run: docker-compose up --build
  • Stop: docker-compose down
  1. Push to a registry (optional)
  • Tag your image for a registry (Docker Hub, GitHub Container Registry, etc.):
    • docker tag myapp:1.0 myusername/myapp:1.0
  • Push:
    • docker push myusername/myapp:1.0
  • Pull on another machine:
    • docker pull myusername/myapp:1.0
  • For private registries, login first: docker login myregistry.example.com

Common pitfalls and tips

  • Missing dependencies: ensure dependencies are installed (RUN npm install) and build runs as part of the image if needed.
  • Large image sizes: use multi-stage builds and alpine-based images; prune dev dependencies.
  • File permissions: avoid running as root; create a non-root user and switch (USER node or similar).
  • Environment-specific configuration: prefer environment variables over hard-coded values; use a config file pattern that reads from env.
  • Port conflicts: ensure the container port matches your Dockerfile EXPOSE and your run command maps to an available host port.
  • CI/CD: consider embedding these steps in your CI pipeline so images are built and tested automatically.
error: Content is protected !!