Ver en Español
Node.js, Express, Docker and Docker Swarm
Jun 24, 2026
Updated: Jun 25, 2026

Node.js, Express, Docker and Docker Swarm

This guide walks you step by step through building a motivational quotes API with Node.js and Express, packaging it in Docker and deploying it to a Docker Swarm cluster. It is designed to be worked through in class, but it also works as a self paced lab.

1. The big picture

Final product: an HTTP service that serves quotes as JSON, packaged in a production ready Docker image and deployed to a Swarm with multiple replicas.

Key technologies: Node.js 24, Express 5, npm, Docker, Docker Hub and Docker Swarm.

Estimated duration: 3 blocks of 60 minutes (adapt it to your group's pace).

Teaching approach: first we understand the concepts, then we build manually, and finally we automate with containers and orchestration.

A note on orchestration: Docker Swarm still ships with Docker and works perfectly for learning, but today it is in maintenance mode. In production, the orchestration standard is Kubernetes. We use Swarm here because it is the simplest way to understand the concepts of replicas, load balancing and services without the complexity of Kubernetes. Once you have these ideas down, the jump to Kubernetes will feel much more natural.

2. Learning objectives

  • Understand why Express is a minimalist framework and how to structure a project from scratch.
  • Practice creating HTTP endpoints that return JSON and handle simple errors.
  • Tell production and development dependencies apart in package.json.
  • Build a reproducible Docker image, applying good practices such as .dockerignore and npm ci.
  • Publish images to Docker Hub and manage tags.
  • Deploy and scale an application on Docker Swarm, verifying that the replicas serve requests.

3. What you will build

The API will have three main routes:

  • GET /quotes returns all the quotes.
  • GET /quotes/random returns a random quote.
  • GET /quotes/:id returns the quote with the given identifier.

The Docker image will expose the service on port 3000 and will be optimized for ARM/AMD execution thanks to the node:24-alpine base. Finally, the service will be orchestrated in Swarm with distributed replicas.

4. Prerequisites

Background knowledge

  • Fundamentals of modern JavaScript.
  • Basic terminal and Git usage.
  • Basic container concepts (what an image is, what a container is, etc.).

Installed software

  • Node.js 24.x or the current active LTS (download from https://nodejs.org).
  • Docker Desktop (macOS/Windows) or Docker Engine 26+ (Linux).
  • A code editor (VS Code, WebStorm, etc.).

Accounts and access

  • A Docker Hub account (free) if you want to publish the image.
  • Access to a cloud or virtual machines (Amazon EC2, Azure, etc.) for the Swarm section.

Quick check

node --version    # should return v24.x.x (or whichever LTS you have installed)
npm --version     # npm ships with Node, version >=10 recommended
docker --version  # Docker Engine >= 26
docker compose version  # optional, recommended

If any command fails, stop and fix the installation before moving on.

5. Setting up the environment

  1. Create a working folder where you will keep all the tutorial material.
  2. Open your terminal and move into that folder.
  3. (Optional) Initialize a Git repository and commit by sections to record your progress.

6. Step 1, initialize the Node.js project

mkdir random-quotes
cd random-quotes
npm init -y
npm install express
npm install --save-dev nodemon

What just happened?

  • npm init -y generates a package.json with default values.
  • express is installed as a production dependency.
  • nodemon is added as a development dependency to automatically reload while you study.

Adjust `package.json`

Edit the package.json file so it contains useful metadata and scripts:

{
  "name": "random-quotes",
  "version": "1.0.0",
  "description": "Educational Express service that serves random quotes and demonstrates Docker & Docker Swarm deployment.",
  "main": "src/server.js",
  "scripts": {
    "start": "node src/server.js",
    "dev": "nodemon src/server.js",
    "lint": "echo \"No linter configured\" && exit 0",
    "test": "echo \"No tests yet\" && exit 0"
  },
  "keywords": [
    "express",
    "docker",
    "tutorial"
  ],
  "author": "Sebastian Gomez <seagomezar@gmail.com> (http://www.sebasgo.dev/)",
  "license": "ISC",
  "engines": {
    "node": ">=22.0.0"
  },
  "dependencies": {
    "express": "^5.1.0"
  },
  "devDependencies": {
    "nodemon": "^3.1.10"
  }
}

Teaching note: defining the scripts from the start helps you run repetitive tasks without memorizing long commands.

7. Step 2, implement the API with Express

Quotes API endpoints table (GET /, /quotes, /quotes/random, /quotes/:id)

We create a simple structure inside src/ to separate responsibilities.

7.1 File `src/quotes.js`

It holds the data and a function that picks a random element. It is a simple way (with no database) to have content.

const quotes = [
  { id: 1, text: 'The best way to predict the future is to invent it.', author: 'Alan Kay' },
  { id: 2, text: 'Simplicity is the soul of efficiency.', author: 'Austin Freeman' },
  { id: 3, text: 'Continuous improvement is better than delayed perfection.', author: 'Mark Twain' },
  { id: 4, text: 'Programs must be written for people to read, and only incidentally for machines to execute.', author: 'Harold Abelson' },
  { id: 5, text: 'If you automate a mess, you get an automated mess.', author: 'Rod Michael' },
  { id: 6, text: 'First, solve the problem. Then, write the code.', author: 'John Johnson' },
  { id: 7, text: 'Any fool can write code that a computer can understand. Good programmers write code that humans can understand.', author: 'Martin Fowler' },
  { id: 8, text: 'Talk is cheap. Show me the code.', author: 'Linus Torvalds' },
  { id: 9, text: 'Experience is the name everyone gives to their mistakes.', author: 'Oscar Wilde' },
  { id: 10, text: 'Success is the ability to go from one failure to another with no loss of enthusiasm.', author: 'Winston Churchill' }
]

function getRandomQuote () {
  const index = Math.floor(Math.random() * quotes.length)
  return quotes[index]
}

module.exports = {
  quotes,
  getRandomQuote
}

7.2 File `src/app.js`

Here we configure Express, define middlewares and routes.

const express = require('express')
const { quotes, getRandomQuote } = require('./quotes')

function createApp () {
  const app = express()

  app.use(express.json())

  app.get('/', (req, res) => {
    res.json({
      status: 'ok',
      message: 'Welcome to the Random Quotes API',
      endpoints: {
        random: '/quotes/random',
        all: '/quotes',
        byId: '/quotes/:id'
      }
    })
  })

  app.get('/quotes', (req, res) => {
    res.json({
      count: quotes.length,
      data: quotes
    })
  })

  app.get('/quotes/random', (req, res) => {
    res.json(getRandomQuote())
  })

  app.get('/quotes/:id', (req, res) => {
    const id = Number(req.params.id)
    const quote = quotes.find(entry => entry.id === id)
    if (!quote) {
      return res.status(404).json({
        error: `Quote with id ${id} not found`
      })
    }
    return res.json(quote)
  })

  return app
}

module.exports = { createApp }

7.3 File `src/server.js`

The service entry point: it creates the app and exposes it on a configurable port.

const { createApp } = require('./app')

const PORT = Number(process.env.PORT) || 3000
const app = createApp()

app.listen(PORT, () => {
  console.log(`Servicio Random Quotes escuchando en el puerto ${PORT}`)
})

8. Step 3, local testing

Development mode (automatic reload):

npm run dev

Production mode (no nodemon):

npm start

Check it with the browser or with curl:

curl http://localhost:3000/
curl http://localhost:3000/quotes/random
curl http://localhost:3000/quotes/3

Teaching shortcuts:

  • Stop the server with Ctrl + C.
  • Use tools like Postman or Insomnia to reinforce API concepts.
  • Try a request to a nonexistent ID (/quotes/999) to observe the error handling.

9. Step 4, good practices before Docker

  1. Create a .dockerignore file so the image is lighter.
  2. Add a .gitignore if you do not have one yet (avoid committing node_modules).
  3. Document the scripts you defined in the README so the team remembers them.

Recommended content for .dockerignore:

node_modules
npm-debug.log
.npmrc
.DS_Store

10. Step 5, building the Docker image

10.1 Dockerfile explained

FROM node:24-alpine AS base

WORKDIR /usr/src/app

COPY package*.json ./

RUN npm ci --omit=dev

COPY . .

ENV NODE_ENV=production

EXPOSE 3000

CMD ["node", "src/server.js"]

Key points:

  • node:24-alpine is lightweight and Linux compatible, ideal for production. If you need a different LTS line, you can use node:22-alpine.
  • npm ci installs exactly the versions defined in package-lock.json and, with --omit=dev, skips the development dependencies (this flag replaces the old, now deprecated --only=production).
  • EXPOSE 3000 documents the port, although the actual publishing is done with docker run -p.
  • We do not need to copy node_modules because they are installed inside the container.

10.2 Build and test the image

docker build -t random-quotes:local .
docker images | grep random-quotes

Run a test container:

docker run --rm -d -p 3000:3000 --name random-quotes-test random-quotes:local
docker logs random-quotes-test    # check that the server started
curl http://localhost:3000/quotes/random
docker stop random-quotes-test    # clean up the container
Terminal running the random-quotes container with docker run, logs and a curl request

Important: if you are on macOS/Windows with Docker Desktop, the port is published on your host machine automatically. On Linux, make sure port 3000 is not already in use.

11. Step 6, publish to Docker Hub

  1. Create a public repository on Docker Hub (for example youruser/random-quotes).
  2. Log in from the terminal:
docker login
  1. Tag the local image with your repository and publish it:
docker tag random-quotes:local youruser/random-quotes:1.0.0
docker push youruser/random-quotes:1.0.0
docker tag random-quotes:local youruser/random-quotes:latest
docker push youruser/random-quotes:latest

Confirm from the Docker Hub web interface that the image was uploaded correctly.

12. Step 7, introduction to Docker Swarm

Docker Swarm is Docker's native orchestrator. It lets you:

  • Create clusters with a manager node (which orchestrates) and multiple worker nodes (which run containers).
  • Define services with multiple distributed replicas.
  • Balance load and automatically restart containers if they fail.

Remember what we said at the start: Swarm is in maintenance mode and in real production the usual choice is Kubernetes. Even so, it is still the clearest way to learn these concepts.

12.1 Prepare the cluster

  1. Make sure you have at least one machine (manager). Ideally three nodes (1 manager, 2 workers) to experiment.
  2. Install Docker on each machine (check your distro's official documentation).
  3. Initialize Swarm on the manager:
docker swarm init --advertise-addr <manager-IP>

Copy the command that gets printed to join workers and run it on each worker machine:

docker swarm join --token <token> <manager-IP>:2377

Verify from the manager:

docker node ls

12.2 Create an overlay network (optional but recommended)

docker network create --driver overlay random-quotes-net

12.3 Deploy the service

docker service create \
  --name random-quotes-service \
  --replicas 3 \
  --publish published=3001,target=3000 \
  --network random-quotes-net \
  youruser/random-quotes:latest

12.4 Monitor the service

docker service ls
docker service ps random-quotes-service
docker service logs random-quotes-service

Tip: if you update the image, use docker service update --image youruser/random-quotes:1.0.1 random-quotes-service.

13. Step 8, validate the Swarm deployment

  1. Get the public IP of the manager node (or the load balancer if you use one).
  2. Open http://<public-IP>:3001/quotes/random in your browser or with curl.
  3. Refresh the page several times to confirm the responses vary (the replicas split the requests).
  4. If you have several machines, send requests from each one to simulate distributed traffic.

When you are done:

docker service rm random-quotes-service
docker network rm random-quotes-net   # if you created it
docker swarm leave --force            # run on each node, the manager last

14. Common troubleshooting

  • Port 3000 is in use: change the PORT environment variable (PORT=4000 npm start) or stop the process using it.
  • `npm install` fails in Docker: make sure you copy package-lock.json and do not edit it by hand.
  • `docker build` is very slow: check your connection; base images can weigh tens of MB.
  • The Swarm service will not start: check docker service logs. If the image does not exist, the worker may not have internet access or the name may be misspelled.
  • Workers do not join the cluster: open ports 2377 (control), 7946 TCP/UDP (node to node communication) and 4789 UDP (overlay network) in the firewall.

15. Extra challenges

  • Add a POST route so the API accepts new quotes in memory.
  • Persistence: store the quotes in a JSON file or a lightweight database (for example, SQLite).
  • Testing: set up Jest or Vitest and write tests for the endpoints.
  • CI/CD: create a GitHub Actions workflow that builds and publishes the image automatically.
  • Observability: integrate a logging middleware like morgan or metrics with Prometheus.

16. Final checklist

  • npm run dev works and shows logs in the console.
  • The endpoints return JSON correctly and handle invalid IDs.
  • The Docker image builds without errors and the container responds on port 3000.
  • The image is published on Docker Hub with at least one semantic tag.
  • The service is deployed on Swarm with several replicas and balances load.
  • You documented all the important commands in your README or log.

17. Quick glossary

  • Node.js: server side JavaScript runtime based on V8.
  • Express: minimalist framework for building APIs and web applications in Node.
  • npm: package manager that installs dependencies from the official registry.
  • Docker Image: immutable template with everything needed to run an application.
  • Docker Container: a running instance of an image.
  • Docker Swarm: Docker's native clustering and orchestration mode.
  • Replica: each copy of a container within a Swarm service.
  • Endpoint: a URL exposed by a service that responds to HTTP requests.

18. Reference repository structure

GitHub repository:

random-quotes
├── README.md
├── Dockerfile
├── package.json
├── package-lock.json
├── .dockerignore
└── src
    ├── app.js
    ├── quotes.js
    └── server.js

That is all, I hope this guide is useful to you and that you can apply it to a project you have in mind, or that it simply helped you understand how Node.js, Express, Docker and Docker Swarm fit together. Leave me a comment if it helped, if you want to add an opinion, or if you have any questions. And remember, if you liked it, you can also share it using the social links below. Good luck.

Sebastian Gomez

Sebastian Gomez

Creador de contenido principalmente acerca de tecnología.

Leave a Reply

0 Comments

Advertisements

Related Posts

Categorias