All posts
DockerDevOpsContainers

Docker Guide: Containerizing a Full-Stack App

A practical guide on how I containerized my portfolio's backend using Docker and Docker Compose, with tips for production deployment.

May 1, 20262 min read

Docker Guide: Containerizing a Full-Stack App

Docker changed the way I think about deploying applications. This guide walks through exactly how I containerized my own portfolio backend — from a single Dockerfile all the way to a multi-service docker-compose.yml.

Why Docker?

Before Docker, deploying meant:

  • Setting up Node.js on the server manually
  • Installing dependencies
  • Praying nothing breaks because of a version mismatch

With Docker, every environment is identical.

Writing the Dockerfile

FROM node:20-alpine

WORKDIR /app

COPY package*.json ./
RUN npm install --production

COPY . .

EXPOSE 3000
CMD ["node", "server.js"]

Key decisions:

  • node:20-alpine — smaller image size than the full Node image
  • Copy package*.json first — leverages Docker's layer caching so npm install only reruns when dependencies change
  • --production flag — don't install devDependencies in the image

Docker Compose for Multi-Service Setup

version: "3.9"

services:
  app:
    build: .
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
      - MONGO_URI=${MONGO_URI}
    depends_on:
      - mongo

  mongo:
    image: mongo:7
    volumes:
      - mongo_data:/data/db

volumes:
  mongo_data:

Running it Locally

# Build and start all services
docker compose up --build

# In detached mode
docker compose up -d

# View logs
docker compose logs -f app

Lessons Learned

  1. Use .dockerignore — exclude node_modules, .git, .env from the image
  2. Never hardcode secrets — use environment variables or Docker secrets
  3. Health checks matter — add HEALTHCHECK to know when your container is actually ready

That's the core workflow. Once you have this down, CI/CD pipelines with GitHub Actions become much simpler.