A practical guide on how I containerized my portfolio's backend using Docker and Docker Compose, with tips for production deployment.
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.
Before Docker, deploying meant:
With Docker, every environment is identical.
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install --production
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]
node:20-alpine — smaller image size than the full Node imagepackage*.json first — leverages Docker's layer caching so npm install only reruns when dependencies change--production flag — don't install devDependencies in the imageversion: "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:
# Build and start all services
docker compose up --build
# In detached mode
docker compose up -d
# View logs
docker compose logs -f app
.dockerignore — exclude node_modules, .git, .env from the imageHEALTHCHECK to know when your container is actually readyThat's the core workflow. Once you have this down, CI/CD pipelines with GitHub Actions become much simpler.