World map

 

BM: Baca dalam Bahasa Melayu

World D2: Recipes

WORKDIR, RUN, ENV and EXPOSE

You will learn: four more recipe lines, and why EXPOSE does not open the hatch.

Checked with Docker Engine 29 on 31 Oct 2026

The problem

A recipe says COPY server.js . and nothing else. The node base sets no work folder. Where does server.js land?

Pick one answer.

Watch

The recipe has WORKDIR /app, COPY of the package files, RUN npm ci, COPY server.js, ENV PORT=3000, EXPOSE 3000 and CMD. After the build, pwd inside the container prints /app. Docker transcript: $ cat Dockerfile FROM node:24-alpine WORKDIR /app COPY package.json package-lock.json ./ RUN npm ci --omit=dev COPY server.js . ENV PORT=3000 EXPOSE 3000 CMD ["node", "server.js"] $ docker build -t api:1.0 . [+] Building 1.3s (10/10) FINISHED docker:default => [internal] load build definition from Dockerfile 0.0s => => transferring dockerfile: 200B 0.0s => [internal] load metadata for docker.io/library/node:24-alpine 0.0s => [internal] load .dockerignore 0.0s => => transferring context: 2B 0.0s => [1/5] FROM docker.io/library/node:24-alpine@sha256:8a32c8cf649a49915a6 0.0s => => resolve docker.io/library/node:24-alpine@sha256:8a32c8cf649a49915a6 0.0s => [2/5] WORKDIR /app 0.0s => [internal] load build context 0.0s => => transferring context: 675B 0.0s => [3/5] COPY package.json package-lock.json ./ 0.0s => [4/5] RUN npm ci --omit=dev 1.2s => [5/5] COPY server.js . 0.0s => exporting to image 0.1s => => exporting layers 0.0s => => exporting manifest sha256:585b1f71ac0676785e33f0a3e3cc27827b1895c98 0.0s => => exporting config sha256:750e26492fb9c3a3ec58b7ad77189f271fe577845a2 0.0s => => exporting attestation manifest sha256:27da90d7c6fda90a44952daaae018 0.0s => => exporting manifest list sha256:8979cf17b5a929255f570efe837792395db7 0.0s => => naming to docker.io/library/api:1.0 0.0s => => unpacking to docker.io/library/api:1.0 0.0s $ docker run --rm api:1.0 pwd /app

Your laptop~/api

  • No open hatches

Lunchboxes (containers)

  • No containers yet

Frozen packs (images)

  • node:24-alpine4 layers · 244MB

0 containers (0 running), 1 images, 0 lockers.

WORKDIR /app is your work counter. Every later line works there. RUN cooks while baking: here it installs packages. pwd prints the counter: /app.

Try it

Some lines bake a layer. Others only stick a note on the pack. Sort them.

Drag a card into a box. Or tap the card, then tap the box.

      Try it

      The image is built. Run it with hatch 3000 open, then knock with curl.

      Your laptop~/api

      • No open hatches

      Lunchboxes (containers)

      • No containers yet

      Frozen packs (images)

      • api:1.08 layers · 244MB
      • node:24-alpine4 layers · 244MB

      0 containers (0 running), 2 images, 0 lockers.

      Predict

      The recipe says EXPOSE 3000. You run the image with no -p. Can your laptop reach port 3000?

      Pick one answer.

      Check

      The api container runs without -p. curl localhost:3000 says: Failed to connect to localhost port 3000: Couldn't connect to server. docker ps shows 3000/tcp with no laptop port. Docker transcript: $ docker build -t api:1.0 . [+] Building 1.3s (10/10) FINISHED docker:default => [internal] load build definition from Dockerfile 0.0s => => transferring dockerfile: 200B 0.0s => [internal] load metadata for docker.io/library/node:24-alpine 0.0s => [internal] load .dockerignore 0.0s => => transferring context: 2B 0.0s => [1/5] FROM docker.io/library/node:24-alpine@sha256:8a32c8cf649a49915a6 0.0s => => resolve docker.io/library/node:24-alpine@sha256:8a32c8cf649a49915a6 0.0s => [2/5] WORKDIR /app 0.0s => [internal] load build context 0.0s => => transferring context: 675B 0.0s => [3/5] COPY package.json package-lock.json ./ 0.0s => [4/5] RUN npm ci --omit=dev 1.2s => [5/5] COPY server.js . 0.0s => exporting to image 0.1s => => exporting layers 0.0s => => exporting manifest sha256:dc7800314dac8d66f0fd744a66287432eab0b43b6 0.0s => => exporting config sha256:00e8092d135c2dc5c2f015b20bb84dce74869227e7d 0.0s => => exporting attestation manifest sha256:9c156a337bb43693fe0cc0122bbdc 0.0s => => exporting manifest list sha256:6e4d6a6a470157a9a2bcaa8ec14418cc8756 0.0s => => naming to docker.io/library/api:1.0 0.0s => => unpacking to docker.io/library/api:1.0 0.0s $ docker run -d --name api api:1.0 9ae7e68b27cc6db02c55bc670db9280e69420649f964a99d83abb26f458d10b0 $ curl localhost:3000 curl: (7) Failed to connect to localhost port 3000 after 0 ms: Couldn't connect to server $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 9ae7e68b27cc api:1.0 "docker-entrypoint.s…" 6 seconds ago Up 6 seconds 3000/tcp api

      Your laptop~/api

      • No open hatches

      Lunchboxes (containers)

      • No containers yet

      Frozen packs (images)

      • node:24-alpine4 layers · 244MB

      0 containers (0 running), 1 images, 0 lockers.

      Real error: Couldn't connect to server. Plain words: no hatch is open. docker ps shows 3000/tcp with no arrow.

      Try it

      ENV PORT=3000 is only a default. Build the command that runs the API on port 4000.

      Tap the pieces in the right order.

      …

      You did it!

      You can now use WORKDIR, RUN, ENV and EXPOSE.

      Go deeper (optional)

      Chain related commands in one RUN with &&. One RUN is one layer. EXPOSE also helps docker run -P, which opens every exposed port on a random laptop port.

      Real mission

      1. docker build -t hello:1.1 .
      2. docker run --rm hello:1.1 pwd

      0 of 3 steps done · Your ticks are saved.