How to Configure Development Containers with Docker

VSCode Dev Containers for React + FastAPI with Docker Compose and hot reloading

·Matija Žiberna·
How to Configure Development Containers with Docker

This was an enourmous source of frustration to set up so I had to document the process.

I was working on a full-stack project when I ran into the classic "it works on my machine" problem.

I couldn't get the development environment set up correctly, and I were spending hours troubleshooting dependency conflicts instead of actually building features.

This guide shows you exactly how to configure development containers that eliminate environment issues and get your entire team productive within minutes instead of days.

I'll demonstrate these concepts using React and FastAPI as examples, but the configuration principles apply to any technology stack - the power lies in mastering the development container setup itself.

What Development Containers Actually Solve

Development containers represent a fundamental shift from traditional development setups. Instead of installing languages, frameworks, and tools directly on your local machine, you develop inside Docker containers that encapsulate everything your project needs. The key insight is that these containers feel exactly like local development while providing perfect consistency across your entire team.

The breakthrough moment came when I realized development containers aren't just about consistency - they're about eliminating the entire category of environment-related problems. No more "works on my machine," no more spending half a day setting up a new developer's environment, and no more conflicts between different projects requiring different versions of the same tools.

Why I Use React + FastAPI to Demonstrate

I'm using a React frontend with Vite and a Python FastAPI backend to demonstrate development container configuration. This isn't about these specific technologies - it's about showing how to configure development containers for any multi-service application.

Why this combination works perfectly as a demonstration:

  • Cross-language complexity: JavaScript/Node.js and Python represent the exact kind of environment management nightmare that development containers solve
  • Real configuration challenges: Different package managers, runtime versions, and development tools - perfect for showcasing comprehensive .devcontainer setup
  • Universal patterns: The configuration techniques you'll learn apply to Vue + Django, Angular + .NET, or any other technology combination

The real focus is mastering .devcontainer configuration. Once you understand how to structure these files properly, you can configure development containers for any technology stack. The React and FastAPI code is just the canvas - the development container configuration is the masterpiece.

What makes development containers powerful:

  • Your entire development environment runs in Docker while maintaining local development performance
  • Every team member works in identical environments regardless of their operating system
  • New developers become productive immediately without complex setup procedures
  • Full VSCode functionality works seamlessly inside containers with IntelliSense, debugging, and extensions
  • Hot reloading and auto-refresh work exactly as expected in traditional development

Prerequisites and Setup

Before diving into the implementation, you'll need three essential tools. The setup process is straightforward, but getting these components working together correctly is crucial for the entire workflow.

Required installations:

  1. Docker Desktop - Download and install from docker.com
  2. VSCode - Get the latest version from code.visualstudio.com
  3. Dev Containers Extension - Install this directly in VSCode by searching for "Dev Containers" in the extensions marketplace

The Dev Containers extension is what makes the magic happen. Once installed, VSCode can connect to and work inside Docker containers as if they were local development environments.

Project Structure: The .devcontainer Configuration is Everything

The development container structure I've settled on supports both independent service development and coordinated multi-service workflows. The most critical part of this entire setup is getting the .devcontainer configurations right - these files are what transform ordinary Docker containers into fully-featured development environments that rival local development.

project-root/
├── .devcontainer/
│   ├── frontend/
│   │   └── devcontainer.json     # Frontend container config
│   └── backend/
│       └── devcontainer.json     # Backend container config
├── frontend/
│   ├── Dockerfile.dev            # Frontend dev environment
│   ├── package.json
│   └── src/
├── backend/
│   ├── Dockerfile.dev            # Backend dev environment
│   ├── requirements.txt
│   └── main.py
└── compose.dev.yml               # Development orchestration

The .devcontainer directory is the heart of this entire system. These VSCode-specific configurations define exactly how each service behaves when opened in a development container - which extensions get installed, what settings are applied, which ports are forwarded, and how the development environment is initialized. Master these configurations, and you've mastered development containers.

Each service gets its own devcontainer.json file with carefully chosen extensions, settings, and development tools. The development Dockerfiles support these configurations by providing the base environment, but the .devcontainer files are what create the actual development experience.

Frontend Development Container Setup

Setting up the frontend development container focuses on creating an optimal React development environment with comprehensive tooling and hot reloading. The goal is to match or exceed the experience of local development while providing perfect consistency across the team.

Frontend Development Dockerfile

Create frontend/Dockerfile.dev:

# Development environment with Node.js
FROM node:20-slim

# Set working directory
WORKDIR /app

# Install git for VSCode and development tools
RUN apt-get update && apt-get install -y \
    git \
    curl \
    && rm -rf /var/lib/apt/lists/*

# Copy package files
COPY package*.json ./

# Install dependencies
RUN npm install --legacy-peer-deps

# Copy source code
COPY . .

# Expose Vite dev server port
EXPOSE 5173

# Start Vite dev server with host binding for Docker
CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0", "--port", "5173"]

This Dockerfile creates a development environment that includes system tools like Git (essential for VSCode functionality) and configures Vite with host binding so the development server is accessible from outside the container. The --host 0.0.0.0 configuration is crucial - without it, you won't be able to access your development server from your host machine.

Frontend Dev Container Configuration

Create .devcontainer/frontend/devcontainer.json:

{
    "name": "Frontend Dev Container",
    "dockerComposeFile": ["../../compose.dev.yml"],
    "service": "frontend",
    "shutdownAction": "none",
    "workspaceFolder": "/workspace/frontend",
    
    "customizations": {
        "vscode": {
            "extensions": [
                // Essential React/TypeScript extensions
                "dbaeumer.vscode-eslint",
                "esbenp.prettier-vscode",
                "bradlc.vscode-tailwindcss",
                "dsznajder.es7-react-js-snippets",
                
                // Development productivity
                "formulahendry.auto-rename-tag",
                "christian-kohler.npm-intellisense",
                "christian-kohler.path-intellisense",
                
                // Git and Docker support
                "eamodio.gitlens",
                "ms-azuretools.vscode-docker",
                
                // Code quality
                "ms-vscode.vscode-typescript-next"
            ],
            "settings": {
                // Editor configuration
                "editor.formatOnSave": true,
                "editor.defaultFormatter": "esbenp.prettier-vscode",
                "editor.codeActionsOnSave": {
                    "source.fixAll.eslint": true,
                    "source.organizeImports": true
                },
                
                // JavaScript/TypeScript settings
                "javascript.updateImportsOnFileMove.enabled": "always",
                "typescript.updateImportsOnFileMove.enabled": "always",
                
                // Terminal configuration
                "terminal.integrated.defaultProfile.linux": "bash",
                
                // File associations
                "files.associations": {
                    "*.css": "tailwindcss"
                }
            }
        }
    },
    
    // Development setup
    "postCreateCommand": "npm install && npm run dev",
    "forwardPorts": [5173],
    "portsAttributes": {
        "5173": {
            "label": "Vite Dev Server",
            "onAutoForward": "notify"
        }
    },
    
    // Use node user for security
    "remoteUser": "node",
    
    // Development features
    "features": {
        "ghcr.io/devcontainers/features/git:1": {},
        "ghcr.io/devcontainers/features/github-cli:1": {}
    }
}

This configuration transforms a basic Docker container into a fully-featured development environment. The extension list includes everything you need for productive React development, while the settings ensure code formatting and linting happen automatically. The postCreateCommand runs when the container is created, automatically installing dependencies and starting the development server.

Backend Development Container Setup

The backend setup creates a comprehensive Python development environment optimized for FastAPI development with debugging capabilities, testing infrastructure, and code quality tools integrated directly into the container.

Backend Development Dockerfile

Create backend/Dockerfile.dev:

# Development environment with Python
FROM python:3.11-slim

# Set working directory
WORKDIR /app

# Install system dependencies for development
RUN apt-get update && apt-get install -y \
    sqlite3 \
    git \
    curl \
    build-essential \
    && rm -rf /var/lib/apt/lists/*

# Copy requirements
COPY requirements.txt .

# Install Python dependencies with development tools
RUN pip install --no-cache-dir -r requirements.txt \
    && pip install --no-cache-dir \
    pytest \
    pytest-asyncio \
    black \
    flake8 \
    mypy \
    isort

# Create data directory
RUN mkdir -p /app/data && chmod 777 /app/data

# Copy application code
COPY . .

# Expose port 8000 for FastAPI
EXPOSE 8000

# Set environment variables
ENV PYTHONUNBUFFERED=1
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONPATH=/app

# Run with Uvicorn in development mode with auto-reload
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--reload", "--log-level", "debug"]

This Dockerfile includes all the development tools you need for professional Python development: pytest for testing, Black for formatting, Flake8 for linting, and MyPy for type checking. The environment variables optimize Python for development with unbuffered output and no bytecode generation. The --reload flag in the CMD ensures the server automatically restarts when you change code.

Backend Dev Container Configuration

Create .devcontainer/backend/devcontainer.json:

{
    "name": "Backend Dev Container",
    "dockerComposeFile": ["../../compose.dev.yml"],
    "service": "backend",
    "shutdownAction": "none",
    "workspaceFolder": "/workspace/backend",
    
    "customizations": {
        "vscode": {
            "extensions": [
                // Python development essentials
                "ms-python.python",
                "ms-python.vscode-pylance",
                "ms-python.black-formatter",
                "ms-python.isort",
                "ms-python.flake8",
                "ms-python.mypy-type-checker",
                
                // Documentation and productivity
                "njpwerner.autodocstring",
                "kevinrose.vsc-python-indent",
                
                // FastAPI specific
                "tamasfe.even-better-toml",
                
                // Development tools
                "eamodio.gitlens",
                "ms-azuretools.vscode-docker",
                
                // Code quality
                "charliermarsh.ruff",
                "ms-python.pytest"
            ],
            "settings": {
                // Python interpreter
                "python.defaultInterpreterPath": "/usr/local/bin/python",
                
                // Linting configuration
                "python.linting.enabled": true,
                "python.linting.pylintEnabled": false,
                "python.linting.flake8Enabled": true,
                "python.linting.mypyEnabled": true,
                
                // Formatting configuration
                "editor.formatOnSave": true,
                "[python]": {
                    "editor.defaultFormatter": "ms-python.black-formatter",
                    "editor.codeActionsOnSave": {
                        "source.organizeImports": true
                    }
                },
                
                // Testing configuration
                "python.testing.pytestEnabled": true,
                "python.testing.unittestEnabled": false,
                "python.testing.pytestArgs": ["tests"],
                
                // IntelliSense settings
                "python.analysis.typeCheckingMode": "basic",
                "python.analysis.autoImportCompletions": true
            }
        }
    },
    
    // Development setup
    "postCreateCommand": "pip install -r requirements.txt && python -c 'import sqlite3; sqlite3.connect(\"/app/data/app.db\").close(); print(\"Database initialized\")'",
    "forwardPorts": [8000],
    "portsAttributes": {
        "8000": {
            "label": "FastAPI Server",
            "onAutoForward": "notify"
        }
    },
    
    // Run as root for development flexibility
    "remoteUser": "root",
    
    // Development features
    "features": {
        "ghcr.io/devcontainers/features/git:1": {},
        "ghcr.io/devcontainers/features/github-cli:1": {}
    }
}

This configuration creates a sophisticated Python development environment with comprehensive tooling integration. The Python extension stack provides IntelliSense, debugging, and code navigation, while the formatting and linting tools maintain code quality automatically. The testing configuration enables running pytest directly from VSCode's interface.

Development Orchestration with Docker Compose

The development compose configuration orchestrates both containers into a cohesive development environment that supports rapid iteration, hot reloading, and seamless service communication.

Development Compose Configuration

Create compose.dev.yml:

services:
  # Frontend development service
  frontend:
    build:
      context: ./frontend
      dockerfile: Dockerfile.dev
    ports:
      - "5173:5173"  # Vite dev server
      - "24678:24678"  # Vite HMR port
    networks:
      - app-network
    volumes:
      # Mount source code for hot reloading
      - .:/workspace:cached
      # Use named volume for node_modules to avoid conflicts
      - frontend-node-modules:/workspace/frontend/node_modules
      # Cache for faster rebuilds
      - frontend-cache:/workspace/frontend/.vite
    environment:
      # Point to backend dev server
      - VITE_API_URL=http://localhost:8000
      - NODE_ENV=development
    stdin_open: true
    tty: true

  # Backend development service
  backend:
    build:
      context: ./backend
      dockerfile: Dockerfile.dev
    ports:
      - "8000:8000"  # FastAPI dev server
      - "5678:5678"  # Python debugger port
    networks:
      - app-network
    volumes:
      # Mount source code for hot reloading
      - .:/workspace:cached
      # Persist backend data
      - backend-data:/app/data
      # Python cache for faster imports
      - backend-cache:/root/.cache/pip
    environment:
      - DATABASE_URL=sqlite:///app/data/app.db
      - ENVIRONMENT=development
      - DEBUG=true
      - PYTHONPATH=/workspace/backend
    stdin_open: true
    tty: true

# Named volumes for performance and persistence
volumes:
  frontend-node-modules:
    driver: local
  frontend-cache:
    driver: local
  backend-data:
    driver: local
  backend-cache:
    driver: local

# Network for service communication
networks:
  app-network:
    driver: bridge

This compose file balances performance, functionality, and developer experience. The volume mounting strategy uses :cached for better performance on macOS, while named volumes for dependencies prevent conflicts and improve build performance. The network configuration enables service-to-service communication using container names.

The Development Workflow

Once everything is set up, the development workflow becomes incredibly smooth. Here's how I work with development containers on a daily basis.

Starting Your Development Environment

# Start all services
docker compose -f compose.dev.yml up -d

# Or start with logs visible for troubleshooting
docker compose -f compose.dev.yml up

Connecting VSCode to Containers

The fastest way to connect is through VSCode's command palette:

  1. Open VSCode in your project root
  2. Press Ctrl+Shift+P (or Cmd+Shift+P on Mac)
  3. Type "Dev Containers: Reopen in Container"
  4. Select either "Frontend Dev Container" or "Backend Dev Container"

VSCode automatically handles container building, starting, and connection. All configured extensions install automatically, and you get a fully configured development environment instantly.

Multi-Service Development

For full-stack development, I typically work with both containers simultaneously:

  1. Open two VSCode windows
  2. Connect one to the frontend container - Access files at /workspace/frontend
  3. Connect the other to the backend container - Access files at /workspace/backend

This setup gives you:

  • Frontend hot reload at http://localhost:5173
  • Backend auto-reload at http://localhost:8000
  • Seamless API communication between services
  • Independent debugging for each service

Daily Development Commands

Frontend development:

# Install new packages
docker compose -f compose.dev.yml exec frontend npm install axios

# Run tests
docker compose -f compose.dev.yml exec frontend npm run test

# Access container shell
docker compose -f compose.dev.yml exec frontend bash

Backend development:

# Install new packages
docker compose -f compose.dev.yml exec backend pip install sqlalchemy

# Run tests
docker compose -f compose.dev.yml exec backend pytest

# Access container shell
docker compose -f compose.dev.yml exec backend bash

Debugging and Development Features

Frontend Debugging

The frontend development experience matches local development exactly:

  • React DevTools work seamlessly with containerized development
  • Hot Module Replacement provides instant feedback without page reloads
  • VSCode debugging with breakpoints works directly in containerized JavaScript
  • Browser DevTools show API calls to the containerized backend normally

Backend Debugging

Backend debugging is equally comprehensive:

  • VSCode Python debugger attaches to Python running in the container
  • Interactive debugging with breakpoint support and variable inspection
  • FastAPI documentation at http://localhost:8000/docs for API testing
  • Real-time logs using docker compose logs -f backend

Troubleshooting Common Issues

Container Won't Start

# Check logs for specific errors
docker compose -f compose.dev.yml logs frontend
docker compose -f compose.dev.yml logs backend

# Rebuild containers from scratch
docker compose -f compose.dev.yml build --no-cache

Hot Reload Not Working

For frontend hot reload issues, add polling to your vite.config.js:

export default {
  server: {
    host: '0.0.0.0',
    port: 5173,
    watch: {
      usePolling: true  // Required for file watching in containers
    }
  }
}

VSCode Can't Connect

# Verify containers are running
docker compose -f compose.dev.yml ps

# Restart Dev Containers extension in VSCode
# Ctrl+Shift+P → "Developer: Reload With Extensions Disabled"
# Then re-enable the Dev Containers extension

Performance Issues

Create a .dockerignore file to exclude unnecessary files from the build context:

node_modules
npm-debug.log
.git
.gitignore
README.md
.env
coverage
.nyc_output

Performance Optimization Tips

Container performance:

  • Use named volumes for node_modules and caches to avoid performance penalties
  • Mount only necessary directories for hot reloading
  • Use .dockerignore to reduce build context size
  • Keep containers running during development to avoid startup overhead

VSCode optimization:

  • Install only necessary extensions to reduce resource usage
  • Use workspace-specific settings to avoid configuration conflicts
  • Configure file watching properly for container environments

This development container setup has transformed how my team develops full-stack applications. We eliminated environment setup time, reduced onboarding from days to minutes, and completely solved the "works on my machine" problem. The initial setup investment pays for itself immediately through improved team productivity and eliminated debugging time.

Let me know in the comments if you have questions about implementing development containers for your team, and subscribe for more practical development guides.

Thanks, Matija

0

Comments

Enjoyed this article?
Subscribe to my newsletter for more insights and tutorials.
Matija Žiberna
Matija Žiberna
Full-stack developer, co-founder

I'm Matija Žiberna, a self-taught full-stack developer and co-founder passionate about building products, writing clean code, and figuring out how to turn ideas into businesses. I write about web development with Next.js, lessons from entrepreneurship, and the journey of learning by doing. My goal is to provide value through code—whether it's through tools, content, or real-world software.

You might be interested in