Build a Minimal Koa Image with a Multi-Stage Dockerfile and a Healthcheck
Containerising with Docker
Write a multi-stage Dockerfile for a Koa ESM app that produces a small, secure image — production dependencies only, non-root user, and a built-in HEALTHCHECK instruction.
What you'll learn
- Write a two-stage Dockerfile that separates build from runtime
- Run the Koa process as a non-root user inside the container
- Add a HEALTHCHECK that polls the /health/live endpoint
Docker lets you ship your Koa app as a self-contained image that runs identically in development, staging, and production. A multi-stage build keeps the final image small by discarding everything not needed at runtime.
Project Structure Assumptions
my-koa-app/
├── src/
│ ├── app.js
│ └── server.js
├── package.json
├── package-lock.json
└── Dockerfile
The app uses ES modules ("type": "module" in package.json) and has no
compile step — if you use TypeScript, add a tsc build stage.
The Dockerfile
# ── Stage 1: install dependencies ────────────────────────────────────────────
FROM node:20-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
# Install only production dependencies
RUN npm ci --omit=dev
# ── Stage 2: runtime image ────────────────────────────────────────────────────
FROM node:20-alpine AS runtime
WORKDIR /app
# Create a non-root user and group
RUN addgroup -S koa && adduser -S koa -G koa
# Copy production node_modules from deps stage
COPY --from=deps /app/node_modules ./node_modules
# Copy application source
COPY src/ ./src/
COPY package.json ./
# Own files as non-root user
RUN chown -R koa:koa /app
USER koa
ENV NODE_ENV=production
ENV PORT=3000
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD wget -qO- http://localhost:3000/health/live || exit 1
CMD ["node", "src/server.js"] Why Multi-Stage?
The first stage (deps) installs everything including devDependencies. The
second stage (runtime) copies only the pruned node_modules from deps,
leaving test runners, build tools, and type definitions out of the final image.
| Image type | Typical size |
|---|---|
node:20 (full Debian) | ~1 GB |
node:20-alpine single-stage | ~200 MB |
| Multi-stage alpine (prod-only) | ~80–120 MB |
Building and Running
# Build
docker build -t my-koa-app:latest .
# Run locally
docker run -p 3000:3000 --env DATABASE_URL=postgres://... my-koa-app:latest
# Check the healthcheck status
docker inspect --format='{{.State.Health.Status}}' <container-id> .dockerignore
Exclude files that should never enter the build context:
node_modules
.env
.env.*
*.test.js
coverage/
.git
This keeps the build context small and prevents accidental secret leakage into the image layer.
Up Next
Explore the wider Koa ecosystem and decide when Koa is (and is not) the right tool.
Going Further with Koa →