---
name: containerise-app-with-docker
description: Packages an app into a small, secure Docker image with a multi-stage Dockerfile - pinned slim base images, cached dependency layers, a .dockerignore, a non-root user and a health check - then builds and runs it locally. Use when the user wants to dockerise an app, write a Dockerfile, shrink a huge image, or run their app in a container.
license: MIT
metadata:
  category: deployment
  tags: "docker,hosting,security"
  author: johnny-bravo
  version: "1.0"
  updated: "2026-11-02"
  status: published
  aliases: "dockerfile, dockerise, docker image, multi-stage build"
  source_url: "https://github.com/github/awesome-copilot/tree/5269f7ea2d6c3a9bfda5ed878b6726cf509121fd/skills/multi-stage-dockerfile"
  source_license: MIT
  copyright: "Copyright GitHub, Inc."
  original_author: "GitHub awesome-copilot contributors (multi-stage-dockerfile)"
  modified: "Adapted by TheSkillVault: turned a bullet checklist into numbered steps with an original Node example, a .dockerignore and run commands; added secrets handling, gotchas and done-when."
  imported_on: "2026-11-02"
---

# Containerise an app with Docker

A container packs your app and everything it needs into one image that runs the same everywhere. Multi-stage builds keep that image small.

## When to use
- A host asks for a Docker image or a Dockerfile.
- "It works on my machine" keeps biting you.
- Your image is over 1 GB and slow to deploy.

## Steps
1. Add a `.dockerignore` listing `node_modules`, `.git`, `.env*`, build output and logs.
2. Pick an official slim base with an exact tag, such as `node:22-slim` or `python:3.12-slim`.
3. Stage one ("deps"): copy only the lockfile and manifest, then install dependencies. This layer caches.
4. Stage two ("build"): copy the source and run the build.
5. Final stage: start from the slim base again and copy only the built output and production dependencies.
6. Create or use a non-root user and switch to it with `USER`.
7. Set `NODE_ENV=production` (or the equivalent), `EXPOSE` the port and add a `HEALTHCHECK` if your host uses it.
8. Build and run: `docker build -t myapp .` then `docker run --env-file .env -p 3000:3000 myapp`.
9. Check the size with `docker images myapp` and scan it with `docker scout cves myapp` or Trivy.

```dockerfile
FROM node:22-slim AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci

FROM deps AS build
COPY . .
RUN npm run build && npm prune --omit=dev

FROM node:22-slim
WORKDIR /app
ENV NODE_ENV=production
COPY --from=build --chown=node:node /app/node_modules ./node_modules
COPY --from=build --chown=node:node /app/dist ./dist
USER node
EXPOSE 3000
CMD ["node", "dist/server.js"]
```

## Gotchas
- Never `COPY .env` or bake API keys into an image. Anyone with the image can read them. Pass secrets at run time.
- Copying all source before installing dependencies breaks caching, so every build reinstalls.
- `latest` tags change under you. Pin versions.
- Alpine images are small but can break native modules. Slim images are the safer default.

## Portability
Works with any language: swap the install and build commands for pip, Go or Maven. If you use Podman instead of Docker, the same Dockerfile works.

## Example prompt
"Write a production Dockerfile for my Node API that is small, runs as non-root and keeps secrets out of the image."

## Done when
- The image builds and the app runs with `docker run`.
- It runs as a non-root user and contains no `.env` file or keys.
- The final image is far smaller than a single-stage build.

---
Adapted from "multi-stage-dockerfile" in github/awesome-copilot (MIT, Copyright GitHub, Inc.). See LICENSE.txt.
