What youβll build
A two-container application: a small Node.js web server, fronted by an Nginx
reverse proxy, orchestrated with Docker Compose. By the end youβll understand
images, containers, port mapping, multi-container networking, and the
docker compose up workflow that real teams use every day.
- Create the project structure
mkdir docker-webapp && cd docker-webapp mkdir appYouβll end up with an
app/folder for the Node server and a couple of config files at the root. - Write a tiny web server
Create
app/server.js:const http = require('http'); const PORT = 3000; http .createServer((_req, res) => { res.writeHead(200, { 'Content-Type': 'text/html' }); res.end('<h1>Hello from a container! π³</h1>'); }) .listen(PORT, () => console.log(`Listening on ${PORT}`));And
app/package.json:{ "name": "docker-webapp", "version": "1.0.0", "main": "server.js" } - Containerize the app
Create
app/Dockerfile:FROM node:20-alpine WORKDIR /app COPY package*.json ./ RUN npm install --omit=dev COPY . . EXPOSE 3000 CMD ["node", "server.js"]Build and test it on its own first:
docker build -t webapp ./app docker run -p 3000:3000 webapp # visit http://localhost:3000, then Ctrl+C - Add an Nginx reverse proxy
Create
nginx.confat the project root:events {} http { server { listen 80; location / { proxy_pass http://app:3000; proxy_set_header Host $host; } } }Note
http://app:3000βappis the service name Compose will create, and containers reach each other by service name on the shared network. - Orchestrate with Docker Compose
Create
docker-compose.ymlat the root:services: app: build: ./app proxy: image: nginx:alpine ports: - "8080:80" volumes: - ./nginx.conf:/etc/nginx/nginx.conf:ro depends_on: - appBring the whole stack up:
docker compose up --buildOpen
http://localhost:8080. Your request now flows browser β Nginx β Node app, across two containers on a private network Compose created for you. - Operate and tear down
docker compose ps # see both services docker compose logs -f # tail combined logs docker compose down # stop and remove everything
Pick one or two to deepen the learning:
- Add a third service β a Redis container β and have the Node app increment and display a visit counter.
- Add a healthcheck to the app service in Compose and watch its status.
- Use a multi-stage build to shrink the image, and compare sizes with
docker images. - Push your image to Docker Hub or GitHub Container Registry.
Capture what you tried, what broke, and the commands that fixed it in the project notes below β that write-up is where the real learning consolidates.