54.10 docker init: generate a Dockerfile, compose.yaml and .dockerignore
Friends, writing a Dockerfile from an empty file is hard for beginners. docker init asks you a few questions and writes a Dockerfile, a compose.yaml, a .dockerignore and a README for you. But generated files are a starting point, not the finish line. Today we run docker init for Node, Python and Go, step by step, and then review and improve what it wrote.
मित्रांनो, रिकाम्या file पासून Dockerfile लिहिणं beginners ना अवघड जातं. docker init काही प्रश्न विचारतो आणि तुमच्यासाठी Dockerfile, compose.yaml, .dockerignore आणि README लिहितो. पण generated files ही सुरुवात आहे, शेवट नाही. आज Node, Python आणि Go साठी docker init step by step चालवूया, आणि त्याने लिहिलेलं review करून सुधारूया.
दोस्तों, खाली file से Dockerfile लिखना beginners के लिए मुश्किल होता है. docker init कुछ सवाल पूछता है और आपके लिए Dockerfile, compose.yaml, .dockerignore और README लिखता है. पर generated files शुरुआत हैं, मंज़िल नहीं. आज Node, Python और Go के लिए docker init step by step चलाएँगे, और उसने जो लिखा उसे review करके सुधारेंगे.
1. Start from a story
At TCS, a new joiner named Rohan was asked to containerise a small Node API that returns movie titles for a client demo. He had never written a Dockerfile.
He copied one from an old blog post. It used node:latest, ran npm install as root, copied node_modules from his laptop and had no .dockerignore, so the build context was 400 MB.
His mentor, Kavita, showed him docker init. In two minutes he had a multi-stage Dockerfile with cache mounts, a non-root user, a sensible .dockerignore and a compose.yaml.
Then Kavita taught him the most important part: read every generated line, pin versions, add a healthcheck and a database, and only then commit. Today we do exactly what Rohan learned.
2. Generate files for a Node app
Step 1: Check docker init is available
Why: It ships with Docker Desktop and recent Engine.
What: docker init is part of Docker Desktop 4.27 and newer. On Linux Engine it comes with the docker-init plugin in recent releases.
Example 1: Version
Type this:
docker init --version
You will see:
Version: v1.4.0
What it means: Any recent version works.
Example 2: Help
Type this:
docker init --help | head -3
You will see:
Usage: docker init [OPTIONS]
Creates Docker-related starter files for your project
What it means: One command, a few questions.
Example 3: If missing
Type this:
docker init 2>&1 | head -1 || true
You will see:
docker: 'init' is not a docker command.
What it means: Then update Docker Desktop, or write files by hand as in earlier lessons.
Step 2: A sample Node project
Why: Something to detect.
What: docker init looks for package.json to know it is Node.
Example 4: Create
Type this:
mkdir titles-api && cd titles-api && printf '%s\n' '{"name":"titles-api","version":"1.0.0","main":"server.js","scripts":{"start":"node server.js"},"dependencies":{"express":"4.21.1"}}' > package.json && echo ok
You will see:
ok
What it means: Express only.
Example 5: Server
Type this:
printf '%s\n' 'const app = require("express")();' 'app.get("/titles", (q, r) => r.json(["Sholay", "Dangal"]));' 'app.get("/health", (q, r) => r.send("ok"));' 'app.listen(3000);' > server.js && echo ok
You will see:
ok
What it means: Two routes.
Example 6: Lock file
Type this:
docker run --rm -v "$PWD":/app -w /app node:20 npm install --package-lock-only -q && ls
You will see:
package-lock.json package.json server.js
What it means: docker init uses npm ci when it sees a lock file.
Step 3: Run docker init
Why: Answer the questions.
What: It detects Node and suggests answers. Press Enter to accept or type your own.
Example 7: Start
Type this:
docker init
You will see:
? What application platform does your project use? Node
? What version of Node do you want to use? 20.18.0
? Which package manager do you want to use? npm
? What command do you want to use to start the app? npm start
? What port does your server listen on? 3000
What it means: Five questions.
Example 8: Files written
Type this:
ls -A
You will see:
..dockerignore Dockerfile README.Docker.md compose.yaml package-lock.json package.json server.js
What it means: Four new files.
Example 9: Will not overwrite silently
Type this:
docker init 2>&1 | head -2
You will see:
! The following Docker files already exist in this directory:
- Dockerfile
What it means: It asks before replacing your files.
Step 4: Read the generated Dockerfile
Why: Understand every line.
What: The file is commented. Look for the base image, the cache mount, the user and the command.
Example 10: Key lines
Type this:
grep -vE '^#|^$' Dockerfile
You will see:
ARG NODE_VERSION=20.18.0
FROM node:${NODE_VERSION}-alpine
ENV NODE_ENV production
WORKDIR /usr/src/app
RUN --mount=type=bind,source=package.json,target=package.json \
--mount=type=bind,source=package-lock.json,target=package-lock.json \
--mount=type=cache,target=/root/.npm \
npm ci --omit=dev
USER node
COPY . .
EXPOSE 3000
CMD npm start
What it means: Good: pinned version, cache mount, non-root user.
Example 11: Spot the issue: shell form CMD
Type this:
grep ^CMD Dockerfile
You will see:
CMD npm start
What it means: Shell form plus npm means SIGTERM may not reach Node.
Example 12: Fix it
Type this:
sed -i 's#^CMD npm start#CMD ["node", "server.js"]#' Dockerfile && grep ^CMD Dockerfile
You will see:
CMD ["node", "server.js"]
What it means: Node is now PID 1 and stops cleanly.
Step 5: Review .dockerignore
Why: Small, safe build context.
What: The generated file ignores node_modules, .git, .env files and Docker files themselves.
Example 13: Contents
Type this:
grep -vE '^#|^$' .dockerignore | head -8
You will see:
**/.classpath
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
What it means: **/.env keeps secrets out.
Example 14: node_modules ignored
Type this:
grep -c node_modules .dockerignore
You will see:
1
What it means: No laptop node_modules in the image.
Example 15: Context size
Type this:
docker build --no-cache -t titles:1.0 . 2>&1 | grep 'transferring context'
You will see:
#5 transferring context: 2.41kB done
What it means: 2 KB instead of Rohan's 400 MB.
Step 6: Build and run with compose
Why: One command.
What: The generated compose.yaml has one server service that builds the Dockerfile and publishes the port.
Example 16: compose.yaml
Type this:
grep -vE '^#|^$' compose.yaml | head -6
You will see:
services:
server:
build:
context: .
environment:
NODE_ENV: production
What it means: Simple and readable.
Example 17: Up
Type this:
docker compose up -d --build 2>&1 | tail -1
You will see:
✔ Container titles-api-server-1 Started
What it means: Built and running.
Example 18: Test
Type this:
curl -s localhost:3000/titles && docker compose exec server id -un
You will see:
["Sholay","Dangal"]
node
What it means: Working and not root.
Step 7: Read README.Docker.md
Why: The generated run notes.
What: docker init also writes a short README with the commands to build, run and deploy. It is a good place to add team notes.
Example 19: Read it
Type this:
head -6 README.Docker.md
You will see:
### Building and running your application
When you're ready, start your application by running:
`docker compose up --build`.
Your application will be available at http://localhost:3000.
What it means: Plain steps for the next developer.
Example 20: Cloud build note
Type this:
grep -m1 -i 'platform' README.Docker.md
You will see:
`docker build --platform=linux/amd64 -t myapp .`
What it means: Reminds Mac M-series users to build for amd64 servers.
Example 21: Add a team note
Type this:
printf '%s\n' '' '### Team notes' '- Secrets live in db/password.txt (never commit).' '- Health URL: /health' >> README.Docker.md && tail -2 README.Docker.md
You will see:
- Secrets live in db/password.txt (never commit).
- Health URL: /health
What it means: Documentation next to the files it explains.
3. Improve it, then try other languages
Step 8: Add a healthcheck
Why: The generator does not add one.
What: Alpine Node images have wget from busybox. Add a compose healthcheck that calls /health.
Example 22: Add it
Type this:
printf '%s\n' ' healthcheck:' ' test: ["CMD", "wget", "-qO-", "http://localhost:3000/health"]' ' interval: 10s' ' retries: 3' > hc.part && sed -i '/^ ports:/e cat hc.part' compose.yaml && docker compose config -q && echo valid
You will see:
valid
What it means: Spliced before the ports key.
Example 23: Status
Type this:
docker compose up -d --wait 2>&1 | tail -1
You will see:
✔ Container titles-api-server-1 Healthy
What it means: --wait waits for healthy.
Example 24: Break it
Type this:
docker compose exec server sh -c 'kill 1'; sleep 2; docker compose ps --format '{{.Status}}'
You will see:
Exited (143) 2 seconds ago
What it means: 143 means SIGTERM; it stopped cleanly thanks to the exec-form CMD.
Step 9: Use the commented database block
Why: Postgres with a secret.
What: The generated compose.yaml has a commented Postgres example with a secret file. Uncomment it instead of writing from scratch.
Example 25: Find it
Type this:
grep -n 'db:' compose.yaml
You will see:
# db:
What it means: Commented out by default.
Example 26: Uncomment
Type this:
sed -i 's/^# //;s/^# //' compose.yaml && mkdir -p db && printf 'example-only-pg' > db/password.txt && chmod 600 db/password.txt && docker compose config --services
You will see:
server
db
What it means: The generator expects db/password.txt.
Example 27: Up with db
Type this:
docker compose up -d --wait 2>&1 | grep db
You will see:
✔ Container titles-api-db-1 Healthy
What it means: Postgres with a pg_isready healthcheck, from the template.
Step 10: Python with docker init
Why: Same flow, different answers.
What: For Python it asks for the version, the port and the start command, and generates a pip cache mount and a non-privileged user.
Example 28: Project
Type this:
mkdir ../quotes-py && cd ../quotes-py && printf '%s\n' 'fastapi==0.115.0' 'uvicorn==0.30.6' > requirements.txt && printf '%s\n' 'from fastapi import FastAPI' 'app = FastAPI()' '@app.get("/")' 'def r(): return {"quote": "Keep learning"}' > app.py && ls
You will see:
app.py requirements.txt
What it means: A FastAPI app.
Example 29: Init
Type this:
docker init
You will see:
? What application platform does your project use? Python
? What version of Python do you want to use? 3.12.7
? What port do you want your app to listen on? 8000
? What is the command you use to run your app? uvicorn app:app --host=0.0.0.0 --port=8000
What it means: --host 0.0.0.0 is important inside containers.
Example 30: User line
Type this:
grep -A6 'ARG UID' Dockerfile | head -7
You will see:
ARG UID=10001
RUN adduser \
--disabled-password \
--gecos "" \
--home "/nonexistent" \
--shell "/sbin/nologin" \
--no-create-home \
What it means: A locked-down user with no shell.
Step 11: Go with docker init
Why: A scratch-style final stage.
What: For Go it writes a build stage with cache mounts and an alpine final stage with a non-root user. You can switch to distroless as in the Go lesson.
Example 31: Project
Type this:
mkdir ../hello-go && cd ../hello-go && printf '%s\n' 'module example.com/hello' '' 'go 1.23' > go.mod && printf '%s\n' 'package main' 'import ("fmt";"net/http")' 'func main(){ http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request){ fmt.Fprint(w, "hi") }); http.ListenAndServe(":8080", nil) }' > main.go && ls
You will see:
go.mod main.go
What it means: A tiny server.
Example 32: Init and check
Type this:
docker init >/dev/null && grep -E '^FROM' Dockerfile
You will see:
FROM --platform=$BUILDPLATFORM golang:${GO_VERSION} AS build
FROM alpine:latest AS final
What it means: Spot it: alpine:latest is not pinned.
Example 33: Pin it
Type this:
sed -i 's#^FROM alpine:latest AS final#FROM alpine:3.20 AS final#' Dockerfile && docker build -q -t hello:1.0 . && docker images hello:1.0 --format '{{.Size}}'
You will see:
15.2MB
What it means: Pinned and small.
Step 12: Scan before you commit
Why: Generated does not mean safe.
What: Scan the image for known vulnerabilities and check the recommended base updates, then commit the files.
Example 34: Quick view
Type this:
docker scout quickview titles:1.0 2>/dev/null | head -3
You will see:
Target │ titles:1.0 │ 0C 0H 2M 1L
digest │ 4f1c2e7a9b31
Base image │ node:20-alpine │ 0C 0H 2M 1L
What it means: No critical or high issues.
Example 35: Recommendations
Type this:
docker scout recommendations titles:1.0 2>/dev/null | grep -m1 Tag
You will see:
Tag │ 20.18.1-alpine
What it means: A newer patch is available.
Example 36: Commit
Type this:
cd ../titles-api && git add Dockerfile compose.yaml .dockerignore README.Docker.md && git status --short | head -4
You will see:
A .dockerignore
A Dockerfile
A README.Docker.md
A compose.yaml
What it means: Never add db/password.txt; add db/ to .gitignore.
4. Common mistakes and how Rohan avoids them
Mistake one: trusting generated files blindly. Rohan reads every line before committing.
Mistake two: leaving shell-form CMD npm start. He switches to exec form with node.
Mistake three: unpinned tags like alpine:latest. He pins exact versions.
Mistake four: no healthcheck. He adds one in compose.yaml.
Mistake five: committing the password file. He keeps db/ in .gitignore.
Check yourself
Q1. Which four files does docker init create?
Dockerfile, compose.yaml, .dockerignore and README.Docker.md.
Q2. How does it know the language?
It looks for files like package.json, requirements.txt or go.mod.
Q3. What does the generator not add?
A healthcheck.
Q4. Why change CMD npm start?
So Node is PID 1 and receives SIGTERM.
Friends, docker init gives you a good first draft in two minutes. Then review it: exec-form CMD, pinned tags, a healthcheck, the database block with a secret file, a scan, and only then commit. Did you understand? Let us try it once more. Run docker init on one of your own projects and list three things you improved.
मित्रांनो, docker init दोन मिनिटांत चांगला पहिला draft देतो. मग review करा: exec-form CMD, pinned tags, healthcheck, secret file सह database block, scan, आणि मगच commit. समजलं का? चला, पुन्हा एकदा करून पाहू. तुमच्या एका project वर docker init चालवा आणि तुम्ही सुधारलेल्या तीन गोष्टी लिहा.
दोस्तों, docker init दो मिनट में अच्छा पहला draft देता है. फिर review कीजिए: exec-form CMD, pinned tags, healthcheck, secret file के साथ database block, scan, और फिर ही commit. समझ आया? चलिए, एक बार फिर करके देखते हैं। अपने किसी project पर docker init चलाइए और जो तीन चीज़ें सुधारीं वे लिखिए.
Ravindra Bagale's Tip
Use docker init to learn. Compare its Dockerfile with the ones from this chapter, and you will quickly see which lines are best practice and which you must change.
शिकण्यासाठी docker init वापरा. त्याचा Dockerfile या chapter मधल्या Dockerfiles शी तुलना करा, म्हणजे कोणत्या lines best practice आहेत आणि कोणत्या बदलायच्या ते पटकन कळेल.
सीखने के लिए docker init इस्तेमाल कीजिए. उसके Dockerfile की तुलना इस chapter के Dockerfiles से कीजिए, तो जल्दी समझ आएगा कि कौन सी lines best practice हैं और कौन सी बदलनी हैं.
Lab: try at home
- Check docker init --version.
- Create the titles-api Node project and run docker init.
- Fix the CMD to exec form and add a healthcheck.
- Uncomment the Postgres block and create the example password file.
- Run docker init for a Python and a Go project and pin the bases.
- Scan with docker scout and commit only the safe files.
Recap
- docker init writes four starter files.
- It detects the language from project files.
- Always review: exec-form CMD and pinned tags.
- Add a healthcheck yourself.
- Use the commented database block with a secret file.
- Scan, then commit, never the password file.