2.5 · Basic security: non-root, secrets and scanning

Level 2 · Dockerfile & build

2.5Basic security: non-root, secrets and scanning

Goal: Harden an image: non-root user, no embedded secret, scanned vulnerabilities.
Estimated time: 12 min · self-paced reading · If you only have 5 min: read “The essentials in 30s” + the diagram.

The essentials in 30 seconds

  • Non-root every time: the container runs as root by default — two lines (dedicated USER) change everything.
  • Zero secrets in the image: a frozen COPY or ENV stays readable in history and leaves on push — inject at run time, never at build time.
  • Official bases + paced scanning: up-to-date slim, scanned CVEs, regular rebuild — security is a rhythm, not a state.

Running mini-case — Idir and the image running as root

Final act: Idir's configurable image (2.4) is ready, but it runs as root, a variables file was lying around in the context, and nobody ever scanned the layers. Idir hardens: official slim base, dedicated user (the one provided by the official image), ownership adjusted at build time, check via exec + whoami — no longer root. He excludes sensitive files from the context (.dockerignore), injects config at run time, then scans and updates the base. Level 2 is delivered: packaged, optimized, versioned, configurable, hardened.

Epilogue of the 2.1-2.4 thread: Idir's app runs non-root, secret-free, scanned — ready for Compose (level 3).

1. No more running as root

By default, the container process is root: if breached, the impact is maximal. Create a dedicated user (the user provided by the official image such as node, ownership adjusted at build time) and declare USER before CMD: two lines, a world of difference. Check with exec … whoami: you must see the dedicated user, not root. Idir adds USER and checks: no more root "to keep it simple".

2. Zero secrets in the image

A COPYed or ENV secret in the Dockerfile stays readable in the layer history and leaves on push with the image. Inject it at run time (-e variable, mounted file, managed secret) and exclude it from the context (.dockerignore). What is never committed nor embedded will never leak from the registry. Idir removes every secret from the Dockerfile and the context: sensitive config travels outside the image.

3. Scan before deploying

docker scout (or Trivy/Grype) lists the layers' CVEs with their severity. Reflex: start from official slim bases, update the tags, rebuild regularly — a 6-month-old image piles up flaws. In production, a variable at run time stays visible via inspect: prefer managed secrets (files, orchestrator, vault). Idir scans, updates, rebuilds, rescans: security is a rhythm.

Common misinterpretation: "USER breaks my permissions, I am going back to root". No: adjust ownership at build time (chown) or align the UID at run time, do not reopen the door. Idir's rule: systematic dedicated USER, permissions fixed cleanly, whoami check — root is never the answer.
A hardened image Smaller base, dedicated user, no secrets, regular scanning Official slim base Smallersurface, up-to-date tagsNon-root USER Dedicateduser, whoami checkedScan + rebuild CVEshandled, steady rhythm
Smaller base, dedicated user, no secrets, regular scanning
Text version of the diagram (SEO / screen readers / translation): 1) Idir SHRINKS: up-to-date official slim base — smaller attackable surface. 2) He DROPS PRIVILEGES: dedicated USER declared before CMD, ownership adjusted at build time, whoami checked via exec — no more root. 3) He PROTECTS: secrets excluded from the context and injected at run time, image scanned (CVEs), regular rebuild. Lesson: non-root + zero secrets + paced scanning is the foundation before prod.

🔎 Concrete example — 🔎 Concrete example — Hardening the Node app

Idir's app, hardened and verified version:

FROM node:20-slim
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
RUN chown -R node:node /app
USER node
EXPOSE 3000
CMD ["node", "server.js"]

docker scout quickview mon-app:1.0
docker exec -it app whoami   # → node, not root

Reading: the app runs as "node", layers are scanned, the port stays declared and published at run time.

Lesson: the .dockerignore excluded sensitive files before the build even started — no embedded secret.

Section vocabulary

USER
The runtime user declared in the Dockerfile (before CMD): the container no longer runs as root.

Simple example: Idir declares USER node before starting his app.

What it is not: the owner of the code repository.

Reflex: dedicated USER before CMD, always.

Secret
Password, key, token: sensitive information that must NEVER enter the image or the repository.

Simple example: Idir injects sensitive config at run time, never at build time.

What it is not: a simple setting like the port.

Reflex: sensitive = out of the image, out of git.

CVE
A public reference of a known vulnerability, with its severity: what scanners list layer by layer.

Simple example: Idir notes the critical CVEs from his scan and updates the base.

What it is not: a Dockerfile configuration mistake.

Reflex: scanning = listing CVEs.

Attack surface
Everything embedded and attackable in the image: shrink it (slim, multi-stage, rebuild).

Simple example: Idir ships slim with no SDK: less embedded, less exposed.

What it is not: the number of launched containers.

Reflex: less embedded = less exposed.

In practice — In practice — Hardening your image

  1. Add a non-root USER to your Dockerfile (before CMD) and rebuild.
  2. Check with exec + whoami (dedicated user, not root).
  3. Scan the image and note the critical CVEs.
  4. Update the base, rebuild, rescan: compare.
Your images run non-root, secret-free, with an up-to-date scan.

Script ready to say — announcing a hardened image (30 s)

"The image runs as a dedicated user, checked with whoami — no more root. No secrets inside: sensitive config is injected at run time and excluded from the context. Official up-to-date base, clean scan, regular rebuild planned. We can push and deploy with confidence."

Say while showing Idir's non-root whoami then the scan report.

Copy-paste template — hardened non-root Dockerfile

FROM node:20-slim
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
RUN chown -R node:node /app
USER node
EXPOSE 3000
CMD ["node", "server.js"]

Broken permission

USER breaks writing to a volume: Idir adjusts ownership at build time instead of going back to root.

Sensitive file embedded

Variables pushed with the image: Idir removes them, .dockerignore + injection at run time.

Forgotten scan

6-month-old image never rescanned: Idir updates the base, rebuilds, rescans — rhythm resumed.

Visible run-time secret

Sensitive variable readable via inspect: Idir switches to managed secrets (files, orchestrator) in prod.

Caution: Mandatory basic security: run your containers as a non-root user (dedicated USER, never root "to keep it simple"), NEVER embed secrets in the image (neither frozen COPY nor ENV: readable in history, pushed to the registry — inject at run time via managed secrets), start from up-to-date official images (slim) and scan before every deployment with regular rebuilds. A public push is irreversible: control the repository visibility before pushing.

✓ Best practices

  • Systematic dedicated USER, checked with whoami.
  • Up-to-date official slim bases.
  • Regular rebuilds (CVE = rhythm).

✗ Common pitfalls

  • Staying root "to keep it simple".
  • Committing a variables file with secrets.
  • Scanning once then forgetting.

Key takeaways

  • Non-root: dedicated USER before CMD, two lines, huge effect.
  • Zero committed or embedded secrets (injected at run time, excluded from the context).
  • We scan (CVEs), update, rebuild regularly.
  • Smaller official base = smaller attack surface.

48h challenge — apply tomorrow

Harden your 1.0 image: non-root USER + whoami checked, .dockerignore with no sensitive files, scan + update + rebuild compared.

Section exit checklist
  • I can run an image non-root (USER + whoami checked).
  • I can exclude every secret from the image and the context (injected at run time).
  • I scan, update and rebuild on a steady rhythm.
Self-assessment 1-5: does whoami answer anything besides root?does my history hold no secrets?

Frequently asked questions

USER breaks my permissions, what do I do?

Often a volume mounted as root: adjust ownership (chown) at build time or align the UID at run time. Do not go back to root for ease: fix the permission cleanly and recheck with whoami.

Is ENV with a password at run time safe?

Better than in the image, but visible via inspect: in production prefer managed secrets (mounted files, orchestrator, vault). Run time is only a step, not a protection.

Check your understanding

Which image will leak a secret on push?

Additional resources (optional, to go further)

Level 2 examFresh, harder questions to test what you've learned