World map

 

BM: Baca dalam Bahasa Melayu

World D4: Set Meals

Capstone mission 1: Pack it

You will learn: to pack Kedai Kancil so one command starts it on any machine.

Checked with Docker Engine 29 on 31 Oct 2026

The problem

Mission 1: pack it! Kedai Kancil gets its own repo, kedai-kancil. First, put each file in its folder.

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

        Watch

        <h1>Kedai Kancil</h1>
        FROM nginx:1.30-alpine
        COPY index.html /usr/share/nginx/html/
        The web recipe bakes, runs on hatch 8080 and serves the page. Docker transcript: $ docker build -t web-check ./web [+] Building 0.1s (7/7) FINISHED docker:default => [internal] load build definition from Dockerfile 0.0s => => transferring dockerfile: 99B 0.0s => [internal] load metadata for docker.io/library/nginx:1.30-alpine 0.0s => [internal] load .dockerignore 0.0s => => transferring context: 2B 0.0s => [1/2] FROM docker.io/library/nginx:1.30-alpine@sha256:0e52bd054126cdc0 0.0s => => resolve docker.io/library/nginx:1.30-alpine@sha256:0e52bd054126cdc0 0.0s => [internal] load build context 0.0s => => transferring context: 114B 0.0s => [2/2] COPY index.html /usr/share/nginx/html/ 0.0s => exporting to image 0.1s => => exporting layers 0.0s => => exporting manifest sha256:b3c874d08f54d4288d6e202ad09fed2c299a3a2d3 0.0s => => exporting config sha256:218b2f32af3a24770dbf4b630e3211e6ed645d1c2ed 0.0s => => exporting attestation manifest sha256:e863f2bf191f80c8a6d4c754d1893 0.0s => => exporting manifest list sha256:6a4cc4692137c3473c4d97518343d7e57258 0.0s => => naming to docker.io/library/web-check:latest 0.0s => => unpacking to docker.io/library/web-check:latest 0.0s $ docker run -d --name webcheck -p 8080:80 web-check cd9f5dce65a5cc9bc156b845b94be94cf80d7938c8932cbc5c0a7e0fbab85bff $ curl -s localhost:8080 <h1>Kedai Kancil</h1>

        Your laptop~/kedai-kancil

        • No open hatches

        Lunchboxes (containers)

        • No containers yet

        Frozen packs (images)

        • nginx:1.30-alpine8 layers · 93.5MB
        • node:24-alpine4 layers · 244MB
        • postgres:18-alpine9 layers · 433MB
        • redis:8-alpine7 layers · 162MB

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

        Top: web/index.html. Below: web/Dockerfile. Two lines: start from nginx, copy your page in.

        Watch

        The API code is below: api/server.js, then api/package.json. Its recipe will have no npm ci step. Why is that fine?

        Pick one answer.

        const http = require('node:http');
        const port = process.env.PORT || 3000;
        http.createServer((req, res) => {
          res.setHeader('Content-Type', 'application/json');
          res.end(JSON.stringify({ ok: true, shop: 'Kedai Kancil' }));
        }).listen(port, () => console.log(`API listening on port ${port}`));
        {
          "name": "kedai-api",
          "version": "1.0.0",
          "private": true,
          "scripts": {
            "start": "node server.js",
            "test": "node --test"
          }
        }

        Watch

        FROM node:24-alpine
        WORKDIR /app
        COPY package.json server.js ./
        USER node
        EXPOSE 3000
        CMD ["node", "server.js"]
        The API recipe bakes, runs on hatch 3000 and answers with JSON. Docker transcript: $ docker build -t api-check ./api [+] Building 0.1s (8/8) FINISHED docker:default => [internal] load build definition from Dockerfile 0.0s => => transferring dockerfile: 149B 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/3] FROM docker.io/library/node:24-alpine@sha256:8a32c8cf649a49915a6 0.0s => => resolve docker.io/library/node:24-alpine@sha256:8a32c8cf649a49915a6 0.0s => [2/3] WORKDIR /app 0.0s => [internal] load build context 0.0s => => transferring context: 594B 0.0s => [3/3] COPY package.json server.js ./ 0.0s => exporting to image 0.1s => => exporting layers 0.0s => => exporting manifest sha256:ecd8cebaf12324ae7edc5eebf8ea8b6f70dde0a63 0.0s => => exporting config sha256:0f4cd63b9dc293dd2a0b8d97cc8ff5be787071df20d 0.0s => => exporting attestation manifest sha256:ca7f87904d894124a997e273b44f6 0.0s => => exporting manifest list sha256:a2c9cac6336d5000d5d12a8390fcba85ef87 0.0s => => naming to docker.io/library/api-check:latest 0.0s => => unpacking to docker.io/library/api-check:latest 0.0s $ docker run -d --name apicheck -p 3000:3000 api-check dfc34f810c89519036a62b448ed4485431d26722c5bf416f378893000f0190f1 $ curl -s localhost:3000 {"ok":true,"shop":"Kedai Kancil"}

        Your laptop~/kedai-kancil

        • No open hatches

        Lunchboxes (containers)

        • No containers yet

        Frozen packs (images)

        • nginx:1.30-alpine8 layers · 93.5MB
        • node:24-alpine4 layers · 244MB
        • postgres:18-alpine9 layers · 433MB
        • redis:8-alpine7 layers · 162MB

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

        This is api/Dockerfile. USER node runs the API as a normal user, not root.

        Try it

        Now the set meal. Add web, api and db. Give db the locker db-data. Make api wait until db is healthy. The cache is optional.

        Build a set meal: add the services, then join their phone lines and lockers.

        Menu (tap or drag)

        Your set meal

        Empty. Tap a dish on the menu, or drag it here.

          compose.yaml 1 lines

          services: {}
          

          Try it

          Now do it for real, step by step. Your ticks are saved.

          Real mission

          1. mkdir -p kedai-kancil/web kedai-kancil/api && cd kedai-kancil && git init
          2. docker compose config --quiet
          3. docker compose up -d --build
          4. docker compose ps
          5. curl localhost:8080
          6. git remote add origin https://github.com/YOUR-NAME/kedai-kancil.git
          7. git add . && git commit -m "Pack the shop" && git branch -M main && git push -u origin main
          8. docker compose down -v
          9. git clone https://github.com/YOUR-NAME/kedai-kancil.git fresh && cd fresh && docker compose up -d

          0 of 11 steps done · Your ticks are saved.

          Check

          GitHub can confirm part of it. Type your username and press Check now.

          Check it on GitHub

          Your username is sent only to api.github.com.

          • Your kedai-kancil repo is publicNot checked yet
          • It has compose.yamlNot checked yet
          • It has web/DockerfileNot checked yet
          • It has api/DockerfileNot checked yet

          Check

          The rest only you can see. Tick what is true.

          Self-check

          0 of 5 steps done · Your ticks are saved.

          You did it!

          You can now pack a whole app so one command starts it anywhere.

          Go deeper (optional)

          Where the story breaks. A real set meal comes on one tray. Compose dishes are separate boxes. They share only phone lines and lockers. A meal is eaten once; a box can stop and serve again. Compose also cooks in one kitchen only. Many machines need the food court: Part 3.

          Mission 2 (World C2) adds a green line.