Docker for Application Developers: A Practical Guide Without the DevOps Jargon

Docker solves the 'works on my machine' problem and gives you reproducible development environments. Here's what you actually need to know as an application developer.

Mahmudul Haque Qudrati — CEO & ML Engineer at Pristren

Mahmudul Haque Qudrati

CEO & ML Engineer

May 17, 2026
10 min read
Docker for Application Developers: A Practical Guide Without the DevOps Jargon

Docker solves two specific problems for application developers: the "works on my machine" problem (your app works locally but fails on CI or in production because of environment differences), and the multi-service local development problem (your app needs a database, a Redis instance, and a mail server running locally). For these two use cases, Docker is the right tool. For everything else, it may be overkill.

Core Concepts Without the Jargon

One AI engineering post, weekly

LLM benchmarks, prompt techniques, and token-cost breakdowns — not another AI news roundup.

Image vs. container. A Docker image is a snapshot of a filesystem and configuration - think of it as a recipe. A container is a running instance of an image - the actual running process. One image can spawn many containers. When you run docker run postgres, Docker pulls the Postgres image (if not already downloaded) and starts a container from it.

Dockerfile. A text file that describes how to build your image. It starts from a base image, copies your code in, installs dependencies, and defines how to start your app. Docker reads this file and produces an image.

docker-compose. A tool for running multiple containers together. Your web app container, database container, and Redis container are defined in one docker-compose.yml file. docker compose up starts all of them.

A Practical Dockerfile for a Node.js App

# Build stage
FROM node:20-alpine AS builder
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN npm install -g pnpm && pnpm install --frozen-lockfile
COPY . .
RUN pnpm build

# Production stage
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production

COPY --from=builder /app/.next ./.next
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./package.json
COPY --from=builder /app/public ./public

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

This is a multi-stage build. The builder stage installs all dependencies and compiles the app. The runner stage copies only the compiled output and production dependencies - discarding the build tools and dev dependencies. The result is a much smaller production image (often 200-400 MB instead of 800+ MB).

Key principles in this Dockerfile:

  • node:20-alpine uses Alpine Linux as the base, which is minimal and small
  • Copy package.json and the lockfile before copying source code - this lets Docker cache the pnpm install layer and skip it when only source code changes
  • The --frozen-lockfile flag ensures the exact dependency versions in your lockfile are installed, giving you reproducibility

Team workspace

Ship faster with chat, meetings, and projects in one place — Zlyqor.

Start free

docker-compose for Multi-Service Local Development

version: "3.9"

services:
  web:
    build: .
    ports:
      - "3000:3000"
    environment:
      - DATABASE_URL=mongodb://mongo:27017/myapp
      - REDIS_URL=redis://redis:6379
    depends_on:
      - mongo
      - redis
    volumes:
      - .:/app
      - /app/node_modules

  mongo:
    image: mongo:7
    ports:
      - "27017:27017"
    volumes:
      - mongo-data:/data/db

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"

volumes:
  mongo-data:
docker compose up          # Start all services
docker compose up -d       # Start in background (detached)
docker compose down        # Stop all services
docker compose logs -f web # Follow logs for the web service

The volumes: .:/app mount in the web service maps your local source code into the container. Combined with a Node.js hot-reload setup (nodemon or Next.js dev mode), changes to local files immediately reflect in the running container - no rebuild required.

The Practical Development Workflow

For local development with Docker Compose:

  1. Write your code locally in your editor (VS Code, Cursor, whatever)
  2. docker compose up starts your app and all dependencies
  3. Edit files - hot reload keeps the running container current
  4. When you add a dependency, rebuild with docker compose up --build
  5. docker compose down when done

This gives every developer on your team the same running environment regardless of their OS. A new developer can be running your app in 5 minutes with git clone and docker compose up, without installing Node, MongoDB, or Redis.

Multi-Stage Builds in Practice

Multi-stage builds keep your production images small and secure. The pattern:

# Stage 1: Install and build
FROM node:20-alpine AS deps
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN npm install -g pnpm && pnpm install

FROM node:20-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN pnpm build

# Stage 2: Minimal production image
FROM node:20-alpine AS runner
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
CMD ["node", "dist/index.js"]

The final runner image contains no build tools, no dev dependencies, no source code - only the compiled output and the runtime dependencies it needs. This reduces the attack surface and the image size.

When Docker Is Overkill for Local Dev

Docker for local development adds overhead: build times, memory usage, and configuration complexity. For a solo developer working on a Next.js app with only a MongoDB dependency, running MongoDB locally via Homebrew (brew services start mongodb-community) is faster and simpler.

Docker for local dev earns its cost when:

  • Your team has 3+ developers on different OSes
  • Your app has 3+ service dependencies
  • A new developer setup currently takes more than an hour
  • Environment differences are causing bugs (prod issues you can't reproduce locally)

Docker Desktop vs Alternatives on macOS

Docker Desktop is the most straightforward way to run Docker on macOS. It is free for personal use and small companies (under 250 employees and $10M revenue). For larger companies, it requires a paid license starting at $21/month per user.

OrbStack is the practical alternative on macOS. It runs Docker containers and Kubernetes, is significantly faster to start than Docker Desktop (3-4 seconds vs 30+ seconds), uses less memory, and costs $8/month after a free trial. For developers who run Docker every day, OrbStack is the better macOS experience.

Colima is a free, open source alternative to Docker Desktop's VM layer. It runs the Docker engine via a lightweight Linux VM. Less polished than OrbStack but zero cost.

.dockerignore - Don't Forget This

Without a .dockerignore file, Docker copies everything including node_modules, .git, .env files, and build artifacts into the image context. This makes builds slow and images large.

node_modules
.next
.git
.env
.env.local
*.log
coverage
.DS_Store

Keep Reading


Pristren builds AI-powered software for teams. Zlyqor is our all-in-one workspace - chat, projects, time tracking, AI meeting summaries, and invoicing - in one tool. Try it free.

#docker#containers#docker-compose#developer-tools#devops

// discussion

Comments

0/4000
Mahmudul Haque Qudrati — CEO & ML Engineer at Pristren

Mahmudul Haque Qudrati

CEO & ML Engineer

Visionary technologist, software engineer, and machine learning specialist. Founder and CEO of Pristren, directing engineering teams that ship production-grade AI/ML pipelines, mission-critical full-stack applications, and developer tooling. Creator of Zlyqor, the unified team workspace platform. Author of 540+ technical guides and benchmark research reports on large language models, agentic workflows, Model Context Protocol (MCP), and modern web stacks.

PristrenZlyqor