Skip to main content

Fullstack Docker Application - Complete Build Guide

This guide walks you through building a fullstack application from scratch with Express backend, React frontend, and Docker containerization - ready for production deployment.

Table of Contents

Backend Setup

Express.js server configuration

Frontend Setup

Vite + React application

CORS & Proxy

Understanding cross-origin requests

Docker Backend

Containerizing Express server

Docker Frontend

Multi-stage build for React

Docker Compose

Orchestrating full stack

Backend Setup (Express.js)

Initialize Node.js Project

Create a new directory for the backend:

Install Dependencies

Install Express and CORS:

Configure package.json

Update package.json to use ES modules:
Key Points:
  • "type": "module" enables ES6 import/export syntax
  • "start" script runs the server

Create Express Server

Create index.js:

Test the Backend

Run the server:
Test the API:
Expected response:

Frontend Setup (Vite + React)

Initialize Vite Project

Create a new directory and initialize Vite with React:

Create API-Fetching Component

Update src/App.jsx:

Run Development Server

The app will run on http://localhost:5173 (default Vite port).
CORS Error Incoming! At this point, you’ll likely see a CORS error because:
  • Frontend runs on http://localhost:5173
  • Backend runs on http://localhost:4000
  • Different ports trigger CORS policy

Understanding CORS and Vite Proxy

What is CORS?

Cross-Origin Resource Sharing (CORS) is a browser security feature that blocks requests between different origins (different protocol, domain, or port).
Vite can proxy API requests during development, making them appear to come from the same origin.Advantages:
  • No CORS issues in development
  • Simple relative URLs
  • Works seamlessly with Vite dev server
Limitations:
  • Only works during development (npm run dev)
  • Not available in production build
Configure the backend to allow requests from specific origins.Advantages:
  • Works in production
  • Proper security configuration
  • Full control over allowed origins
Required for:
  • Production builds
  • Direct API calls from browsers
  • Different deployment scenarios

Vite Proxy Setup

Update vite.config.js:
How it works:
  • Requests to /api/* from the frontend are proxied to http://localhost:4000
  • Browser sees requests as same-origin (localhost:5173)
  • No CORS issues during development

Update Frontend Code (With Proxy)

With proxy enabled, update App.jsx:

Fixing CORS on Backend

Why Backend CORS is Necessary

Even with Vite proxy for development, you need backend CORS configuration for:
Production builds Static files served separately
Direct API calls From browsers without proxy
Deployment scenarios Different hosting configurations

Configure Specific Origins

For better security, specify allowed origins:
CORS Options:
  • origin: Array of allowed origins
  • credentials: Allow cookies/authentication headers
  • methods: HTTP methods allowed
  • allowedHeaders: Headers the client can send

Dynamic CORS with Environment Variables

For flexibility across environments:

Dockerizing the Backend

Create Backend Dockerfile

Create server/Dockerfile:
  • FROM node:22-alpine: Lightweight Node.js image
  • WORKDIR /app: Set working directory
  • Copy package*.json first for better Docker layer caching
  • EXPOSE 4000: Document the port (doesn’t actually publish it)
  • CMD: Command to run when container starts

Important: Bind to 0.0.0.0

Update server/index.js to bind to all interfaces:
Why 0.0.0.0?
  • Default localhost binding only accepts connections from within the container
  • 0.0.0.0 accepts connections from outside the container
  • Essential for Docker port mapping

Create .dockerignore

Create server/.dockerignore:
This prevents unnecessary files from being copied into the Docker image.

Build and Test Backend Docker Image

Test:

Dockerizing the Frontend

Create Frontend Dockerfile (Multi-stage Build)

Create client/Dockerfile:
Multi-stage Build Benefits:
  • Build stage: Compiles React app with all dev dependencies
  • Production stage: Serves static files with minimal dependencies
  • Result: Significantly smaller final image size

Create .dockerignore for Frontend

Create client/.dockerignore:

Build and Test Frontend Docker Image

Note: Map host port 5174 to container port 3000
Test: Open http://localhost:5174 in browser

Update Frontend for Production

For production, update src/App.jsx to use environment variables:
Create .env.production:

Docker Compose Setup

Create docker-compose.yml

Create docker-compose.yml in the project root:

Understanding Docker Compose Configuration

  • build: ./server: Build context for server Dockerfile
  • container_name: Custom container name
  • ports: "4000:4000": Map host port 4000 to container port 4000
  • networks: Assign to custom network
  • restart: Auto-restart policy
  • build: ./client: Build context for client Dockerfile
  • ports: "5174:3000": Map host port 5174 to container port 3000
  • depends_on: Wait for server to start first
  • networks: Same network as server for communication
  • fullstack-net: Custom bridge network
  • Allows containers to communicate by service name
  • Provides isolation from other Docker networks

Important Networking Concepts

Container-to-Container

Within Docker network, containers can communicate using service names:

Browser-to-Container

Browsers cannot use Docker service names. They must use:

Run with Docker Compose

Update CORS for Production Deployment

When deploying to a server with a public IP, update backend CORS:

Production Considerations

Environment Variables

Create .env for Docker Compose:
Update docker-compose.yml to use env vars:

Health Checks

Add health checks to ensure services are ready:

Complete File Structure


Common Issues and Solutions

Problem: Browser blocks API requests from different origin.Solution:
  • Configure CORS on backend with correct origins
  • Ensure production IP/domain is in allowed origins list
Problem: Container running but can’t access on localhost.Solutions:
  • Ensure Express binds to 0.0.0.0 (not localhost)
  • Check port mappings in docker-compose.yml
  • Verify firewall settings
Problem: Services can’t communicate using service names.Solution:
  • Ensure both services are on the same network
  • Use service names (not localhost) within containers
  • Check network configuration in docker-compose.yml
Problem: Frontend build can’t reach backend API.Solutions:
  • Use environment variables for API URL
  • Update CORS to include production origins
  • Verify backend is accessible from browser (not just containers)

Quick Reference Commands


Summary

This guide covered:

Express Backend

Setup with CORS configuration

React Frontend

Vite setup with API integration

CORS & Proxy

Development and production strategies

Docker Backend

Containerization best practices

Docker Frontend

Multi-stage builds

Docker Compose

Full-stack orchestration

Key Takeaways

  • CORS is essential for browser-based API calls
  • Vite proxy helps in development but production needs backend CORS
  • Docker requires binding to 0.0.0.0 for external access
  • Service names work for container-to-container communication
  • Browsers need localhost or public IPs, not Docker service names

Happy Coding! 🚀