Skip to content
 
 

Latest commit

 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Docker Workshop

Welcome! In this workshop you will take a working web app and learn how to package and run it with Docker — covering images, containers, ports, volumes, environment variables, and networking.


Before You Start

Step 0 — Install Docker

If you do not have Docker installed yet, do this first.

  1. Go to https://www.docker.com/products/docker-desktop
  2. Download Docker Desktop for your operating system (Windows, macOS, or Linux)
  3. Run the installer and follow the prompts
  4. After installation, restart your computer if prompted
  5. Open Docker Desktop and wait for it to show "Engine running"

Verify Docker is working by opening a terminal and running:

docker --version

You should see something like Docker version 26.x.x. If you get an error, ask your instructor.


Part 1 — Run the App Locally (Without Docker)

Before we containerise anything, make sure the app works on your machine.

1.1 — Fork and clone the repo

  1. Click Fork at the top-right of this GitHub page
  2. Clone your fork to your machine:
git clone https://github.com/YOUR-USERNAME/docker-workshop.git
cd docker-workshop

1.2 — Install dependencies

npm install

1.3 — Start the app

npm start

Open your browser and go to: http://localhost:3000

You should see the workshop app. Try adding a note — you will notice that notes are saved to the data/notes.txt file on your machine.

Stop the app with Ctrl + C when you are done.


Part 2 — Your First Dockerfile

A Dockerfile is a text file that describes how to build a Docker image. Think of an image as a snapshot of your app and everything it needs to run.

You will write this file together with your instructor. Create a file called Dockerfile (no extension) in the root of the project.

# Start from an official Node.js base image
FROM node:20-alpine

# Set the working directory inside the container
WORKDIR /app

# Copy dependency files first (layer caching optimisation)
COPY package*.json ./

# Install dependencies
RUN npm install --omit=dev

# Copy the rest of the app source code
COPY . .

# Tell Docker which port the app listens on (documentation only)
EXPOSE 3000

# The command to run when the container starts
CMD ["node", "app.js"]

Key concepts — pause and discuss with your instructor

Instruction What it does
FROM Sets the base image. Every image starts from another image.
WORKDIR Creates and switches into a directory inside the container.
COPY Copies files from your machine into the image.
RUN Executes a command at build time (e.g. installing packages).
EXPOSE Documents that the container listens on a port. Does not actually publish it.
CMD The command that runs when the container starts.

Part 3 — Build an Image

docker build -t workshop-app .
  • -t workshop-app gives the image a name (tag)
  • . tells Docker to look for the Dockerfile in the current directory

List all images on your machine:

docker images

You should see workshop-app in the list.


Part 4 — Run a Container

docker run -p 3000:3000 workshop-app

Open http://localhost:3000 — the app is now running inside a container.

Understanding port mapping (-p)

-p HOST_PORT:CONTAINER_PORT

The container has its own isolated network. Port 3000 inside the container is not automatically available on your machine. The -p flag creates a mapping:

Your browser -> localhost:3000 -> Docker -> container:3000

Try a different host port:

docker run -p 8080:3000 workshop-app

Now visit http://localhost:8080 — same app, different port on your machine.

Stop the container with Ctrl + C, or from another terminal:

docker ps                        # list running containers
docker stop <CONTAINER_ID>       # stop by ID

Part 5 — Run in the Background (Detached Mode)

docker run -d -p 3000:3000 --name my-app workshop-app
  • -d runs the container in the background (detached)
  • --name my-app gives the container a friendly name

Useful commands:

docker ps                   # list running containers
docker logs my-app          # view output logs
docker logs -f my-app       # follow logs in real time
docker stop my-app          # stop the container
docker rm my-app            # remove the stopped container
docker stop my-app && docker rm my-app   # stop and remove in one go

Part 6 — Environment Variables

The app reads environment variables to change its behaviour without rebuilding the image. This is a core Docker best practice.

docker run -d -p 3000:3000 \
  -e GREETING="Hey there from Docker!" \
  -e AUTHOR="Your Name" \
  --name my-app workshop-app

Reload http://localhost:3000 — you should see your custom greeting and name.

Variables the app supports:

Variable Default Effect
PORT 3000 Port the app listens on
GREETING Hello from the workshop app! The greeting shown on the page
AUTHOR Anonymous Author name shown on the page
NOTES_FILE data/notes.txt Path to the notes file

Part 7 — Volume Mounts

The problem: containers are ephemeral

Start the app, add a note, then remove the container:

docker run -d -p 3000:3000 --name my-app workshop-app
# add a note in the browser
docker stop my-app && docker rm my-app
docker run -d -p 3000:3000 --name my-app workshop-app
# notes are gone!

The solution: mount a volume

A volume mount links a directory on your machine to a path inside the container. Writes inside the container go to your machine — and survive restarts.

docker run -d -p 3000:3000 \
  -v "$(pwd)/data:/app/data" \
  --name my-app workshop-app
  • $(pwd)/data — the data/ folder on your machine
  • /app/data — where the app writes notes inside the container

Now add a note, stop and remove the container, start it again with the same mount — your notes persist.

Check that the file was written on your host machine:

cat data/notes.txt

Part 8 — Docker Networking

Every container gets its own isolated network stack by default. Docker provides several networking modes.

Default bridge network

When you run containers without specifying a network, they join Docker's default bridge network. Containers on the same bridge can reach each other by IP, but not by name (unless you use a custom network).

Create a custom network

docker network create workshop-net
docker network ls

Run two containers on the same network

# App container
docker run -d -p 3000:3000 \
  --network workshop-net \
  --name app \
  workshop-app

# A second container (e.g. a utility) can now reach "app" by its name
docker run --rm \
  --network workshop-net \
  alpine \
  wget -qO- http://app:3000

On a custom network, Docker provides automatic DNS resolution — containers can reach each other by their --name. This is how multi-container apps (e.g. web + database) communicate.

Why this matters for your projects

If your web project has a separate database or API service, each will run in its own container. They talk to each other using service names over a shared Docker network — exactly what Docker Compose manages automatically.


Part 9 — Introduction to Docker Compose

Writing long docker run commands gets tedious. Docker Compose lets you define all your containers, ports, volumes, and networks in a single docker-compose.yml file.

You will write this file together with your instructor:

services:
  app:
    build: .
    ports:
      - "3000:3000"
    environment:
      - GREETING=Hello from Compose!
      - AUTHOR=Workshop Student
    volumes:
      - ./data:/app/data
    networks:
      - workshop-net

networks:
  workshop-net:

Start everything with one command:

docker compose up          # foreground
docker compose up -d       # background
docker compose down        # stop and remove containers
docker compose logs -f     # follow logs

Quick Reference

# Images
docker build -t <name> .          # build image from Dockerfile
docker images                     # list images
docker rmi <image>                # remove an image

# Containers
docker run -p 3000:3000 <image>   # run a container
docker run -d ...                 # run detached (background)
docker run -e KEY=VALUE ...       # pass environment variable
docker run -v host:container ...  # mount a volume
docker run --network <net> ...    # attach to a network
docker ps                         # list running containers
docker ps -a                      # list all containers (including stopped)
docker stop <id/name>             # stop a container
docker rm <id/name>               # remove a container
docker logs <id/name>             # view logs
docker exec -it <id/name> sh      # open a shell inside a running container

# Networks
docker network create <name>      # create a network
docker network ls                 # list networks

# Compose
docker compose up -d              # start all services
docker compose down               # stop all services
docker compose logs -f            # stream logs

Troubleshooting

Port already in use

Error: listen EADDRINUSE: address already in use :::3000

Another process is using that port. Either stop it or use a different host port: -p 3001:3000

Docker command not found Docker Desktop is not running. Open it and wait for the engine to start.

Permission denied on volume mount (Linux/macOS) Try running with your current user's UID:

docker run -u $(id -u):$(id -g) ...

Changes to code not reflected after rebuild Make sure you rebuild the image: docker build -t workshop-app . — running docker run again without rebuilding uses the old cached image.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages