This guide explains how to dockerize a full‑stack web application with separate backend and frontend containers, plus PostgreSQL and Redis for data persistence.
my-app/
├── backend/
│ ├── src/
│ │ └── index.ts
│ ├── Dockerfile
│ ├── package.json
│ ├ ── tsconfig.json
│ └── .dockerignore
├── frontend/
│ ├── src/
│ │ └── App.tsx
│ ├── Dockerfile
│ ├── package.json
│ └── .dockerignore
├── docker-compose.yml
├── .env
└── .gitignore
Purpose: Describes the build process for the Node.js backend with TypeScript.
FROM node:18-alpine
WORKDIR /app
COPY package.json yarn.lock ./
RUN yarn install --frozen-lockfile
COPY . .
RUN yarn build
CMD ["node", "dist/index.js"]
Purpose: Describes the build process for the React frontend.
FROM node:18-alpine
WORKDIR /app
COPY package.json yarn.lock ./
RUN yarn install --frozen-lockfile
COPY . .
RUN yarn build
CMD ["yarn", "start"]
Purpose: Defines services (backend, frontend, PostgreSQL, Redis), ports, environment, and volumes.
version: '3.8'
services:
backend:
build: ./backend
ports:
- "3000:3000"
environment:
- DATABASE_URL=postgres://user:password@db:5432/myapp
- REDIS_HOST=redis
- NODE_ENV=production
depends_on:
- db
- redis
volumes:
- ./backend:/app
frontend:
build: ./frontend
ports:
- "80:3000"
environment:
- REACT_APP_API_URL=http://backend:3000
depends_on:
- backend
db:
image: postgres:13
environment:
- POSTGRES_USER=user
- POSTGRES_PASSWORD=password
- POSTGRES_DB=myapp
ports:
- "5432:5432"
volumes:
- pgdata:/var/lib/postgresql/data
redis:
image: redis:6
ports:
- "6379:6379"
volumes:
- redisdata:/data
volumes:
pgdata:
redisdata:
Purpose: Stores environment variables (do not commit to Git).
DATABASE_URL=postgres://user:password@db:5432/myapp
REDIS_HOST=redis
API_KEY=your-secret-key
Purpose: Excludes unnecessary files from being copied.
node_modules
dist
.git
.env
Dockerfile
sudo apt install docker-compose
).Verify:
docker --version
docker-compose --version
All services share a network and access each other by service name.
Example (backend/src/index.ts):
import express from 'express';
import { Pool } from 'pg';
import { createClient } from 'redis';
const app = express();
const port = 3000;
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
const redisClient = createClient({ url: `redis://${process.env.REDIS_HOST}:6379` });
redisClient.connect().catch(console.error);
app.get('/data', async (req, res) => {
const cached = await redisClient.get('data');
if (cached) return res.json({ source: 'redis', data: JSON.parse(cached) });
const { rows } = await pool.query('SELECT NOW()');
await redisClient.setEx('data', 3600, JSON.stringify(rows));
res.json({ source: 'db', data: rows });
});
app.listen(port, () => console.log(`Backend on port ${port}`));
Ports map host:container (e.g., 3000:3000
). Environment variables are set in docker-compose or pulled from .env
.
docker-compose up --build
: build & start all servicesdocker-compose up -d
: start in backgrounddocker-compose down
: stop all (add --volumes
to remove data)docker-compose logs backend
: view backend logsdocker-compose restart backend
: restart backendModify code and run docker-compose up --build backend
.
docker build -t myapp-backend:1.0.0 ./backend
docker login
docker tag myapp-backend:1.0.0 yourusername/myapp-backend:1.0.0
docker push yourusername/myapp-backend:1.0.0
docker-compose.yml
to use image: yourusername/myapp-backend:1.0.0
, then docker-compose pull backend && docker-compose up -d backend
.Use GitHub Actions to build & push images on push to main:
name: Build and Push Docker Image
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build & push Docker image
run: |
docker build -t yourusername/myapp-backend:${{ github.sha }} ./backend
docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
docker push yourusername/myapp-backend:${{ github.sha }}