Build images

Create the Notes API

Create the small Node.js HTTP service that will become the running project for the rest of the Docker course.

For the rest of the course we’ll containerize one small project: a Notes API. It’s a Node.js HTTP server that returns a list of notes as JSON.

I keep it deliberately tiny. When something breaks, I want to know at a glance whether the problem comes from Node.js or from Docker. A big app hides that.

The server

Create a folder called notes-api, and inside it a server.js file:

import { createServer } from 'node:http'

const notes = [{ id: 1, text: 'Learn containers' }]
const port = Number(process.env.PORT ?? 3000)

createServer((request, response) => {
  response.setHeader('content-type', 'application/json')
  response.end(JSON.stringify({ notes }))
}).listen(port, '0.0.0.0')

Two details in here are there because of Docker, not because of Node.

The port comes from the PORT environment variable, with 3000 as a fallback. Docker configures containers through environment variables, so reading the port this way lets us change it later without touching the code.

The second detail is '0.0.0.0'. Let’s talk about that.

Why 0.0.0.0 and not localhost

Inside a container, localhost (127.0.0.1) means “this container only”. If the server binds to 127.0.0.1, it accepts connections that start inside the container. Requests from your machine arrive through Docker’s virtual network interface, and they get refused. The container looks alive, the port is published, and curl still fails. It’s a classic.

Binding to 0.0.0.0 means “accept connections on every interface of this container”, including the one Docker uses to deliver traffic. That’s the setting you want in almost every container.

package.json

Add a package.json next to the server so Node runs the file as an ES module:

{
  "name": "notes-api",
  "type": "module"
}

Run it without Docker first

Always test the plain process before containerizing it:

node server.js

In a second terminal:

curl http://localhost:3000

You should get:

{"notes":[{"id":1,"text":"Learn containers"}]}

Stop it with Ctrl+C. Now you have a known-good baseline. When we run the same curl against the container in the next lessons, the only new thing between us and the response is Docker. If it fails, we know where to look.

Keep it observable

A few habits pay off once the server lives in a container. If PORT isn’t a valid number, fail at startup with a clear message. Log startup errors to standard error, so docker logs shows them. Let the process exit when something is really wrong, instead of swallowing the error, because Docker only watches the main process. And don’t push real work into detached child processes, since Docker can’t see those either.

Lesson completed