Back to blog

Modern Development Environments with Docker

4 min readBy Mustafa Akkaya
#Docker#DevOps#Development#Best Practices

One of the biggest challenges in modern software development is solving the "it works on my machine" problem. Docker eliminates this issue by enabling us to create consistent and portable development environments.

Benefits of Docker for Development

Docker offers numerous advantages in the development process:

- Consistency - The entire team works in the same environment

- Isolation - Projects don't interfere with each other

- Quick Setup - New developers can start in minutes

- Production-like - Minimize differences between development and production

- Clean System - Host system remains clean

- Easy Version Management - Different technology versions are easily managed

Core Docker Concepts

Dockerfile - Image Definition

A Dockerfile defines how your application will run:

Dockerfile

# Example Dockerfile class="keyword">class="keyword">for Node.js application

class="keyword">FROM node:20-alpine class="keyword">AS base

# Create working directory

WORKDIR /app

# Copy package files

COPY package*.json ./

# Install dependencies

RUN npm ci

# Copy application code

COPY . .

# Port information

EXPOSE 3000

# Startup command

CMD [class="keyword">class="string">"npm", class="keyword">class="string">"run", class="keyword">class="string">"dev"]

Multi-Stage Builds

Create optimized images for production:

Dockerfile

# Build stage

class="keyword">FROM node:20-alpine class="keyword">AS builder

WORKDIR /app

COPY package*.json ./

RUN npm ci

COPY . .

RUN npm run build

# Production stage

class="keyword">FROM node:20-alpine class="keyword">AS production

WORKDIR /app

COPY package*.json ./

RUN npm ci --only=production

COPY --class="keyword">from=builder /app/dist ./dist

EXPOSE 3000

CMD [class="keyword">class="string">"node", class="keyword">class="string">"dist/server.js"]

Multi-Service Management with Docker Compose

Docker Compose is ideal for managing multiple services.

Next.js + PostgreSQL + Redis Example

config.yaml

version: class="keyword">class="string">"3.8"

services:

app:

build:

context: .

dockerfile: Dockerfile.dev

ports:

- class="keyword">class="string">"3000:3000"

volumes:

- .:/app

- /app/node_modules

environment:

- DATABASE_URL=postgresql:class="keyword">class="comment">//postgres:password@db:5432/myapp

- REDIS_URL=redis:class="keyword">class="comment">//redis:6379

depends_on:

- db

- redis

command: npm run dev

db:

image: postgres:16-alpine

ports:

- class="keyword">class="string">"5432:5432"

environment:

- POSTGRES_USER=postgres

- POSTGRES_PASSWORD=password

- POSTGRES_DB=myapp

volumes:

- postgres_data:/class="keyword">class="keyword">var/lib/postgresql/data

redis:

image: redis:7-alpine

ports:

- class="keyword">class="string">"6379:6379"

volumes:

- redis_data:/data

volumes:

postgres_data:

redis_data:

Dockerfile for Development

Dockerfile

# Dockerfile.dev

class="keyword">FROM node:20-alpine

WORKDIR /app

# Copy package files

COPY package*.json ./

# Install dependencies(including dev dependencies)

RUN npm install

# Install nodemon class="keyword">class="keyword">for hot reload

RUN npm install -g nodemon

EXPOSE 3000

CMD [class="keyword">class="string">"npm", class="keyword">class="string">"run", class="keyword">class="string">"dev"]

Best Practices

1. Use .dockerignore

Exclude unnecessary files from the image:

code.dockerignore

node_modules

npm-debug.log

.env

.env.local

.git

.gitignore

README.md

.next

.DS_Store

dist

build

coverage

2. Leverage Layer Caching

Copy files that change less frequently first:

Dockerfile

# First dependencies(changes less often)

COPY package*.json ./

RUN npm ci

# Then code(changes frequently)

COPY . .

3. Add Health Checks

Dockerfile

HEALTHCHECK --interval=30s --timeout=3s --start-period=40s \

CMD node healthcheck.js || exit 1

4. Use Non-Root User

Dockerfile

# Use non-root user class="keyword">class="keyword">for security

RUN addgroup -g 1001 -S nodejs

RUN adduser -S nextjs -u 1001

USER nextjs

Common Use Cases

Working with Database

script.sh

# Start services

docker-compose up -d

# Run database migrations

docker-compose exec app npx prisma migrate dev

# Connect to database

docker-compose exec db psql -U postgres -d myapp

Viewing Logs

script.sh

# View all logs

docker-compose logs

# Only app logs

docker-compose logs app -f

# Last 100 lines

docker-compose logs --tail=100

Cleanup and Restart

script.sh

# Stop and clean services

docker-compose down

# Also remove volumes

docker-compose down -v

# Rebuild and start

docker-compose up --build

Docker Integration with VS Code

Develop directly inside containers with VS Code Dev Containers extension:

data.json

class="keyword">class="comment">// .devcontainer/devcontainer.json

{

class="keyword">class="string">"name": class="keyword">class="string">"Next.js App",

class="keyword">class="string">"dockerComposeFile": class="keyword">class="string">"../docker-compose.yml",

class="keyword">class="string">"service": class="keyword">class="string">"app",

class="keyword">class="string">"workspaceFolder": class="keyword">class="string">"/app",

class="keyword">class="string">"customizations": {

class="keyword">class="string">"vscode": {

class="keyword">class="string">"extensions": [

class="keyword">class="string">"dbaeumer.vscode-eslint",

class="keyword">class="string">"esbenp.prettier-vscode",

class="keyword">class="string">"bradlc.vscode-tailwindcss"

]

}

}

}

Performance Tips

1. Use Build Cache

script.sh

# Fast build with BuildKit

DOCKER_BUILDKIT=1 docker build -t myapp .

2. Prefer Named Volumes

config.yaml

volumes:

# Instead of anonymous volume

- node_modules:/app/node_modules

3. Optimize Bind Mounts

config.yaml

volumes:

- .:/app:delegated # Performance class="keyword">class="keyword">for macOS

- /app/node_modules # Isolate node_modules class="keyword">from host

Conclusion

Docker is essential for modern development environments. When used correctly, it:

- Increases team productivity

- Reduces onboarding time

- Minimizes production issues

- Provides a clean and consistent development experience

By integrating Docker into your projects, you can find a permanent solution to the "it works on my machine" problem and significantly improve your development process.

Useful Commands

script.sh

# List running containers

docker ps

# List all containers

docker ps -a

# List images

docker images

# Connect to container with shell

docker-compose exec app sh

# Restart container

docker-compose restart app

# View container resources

docker stats

# Clean up everything unused

docker system prune -a

Happy coding! 🚀