Dockerfile

Base & Metadata

# syntax=docker/dockerfile:1
FROM node:20-alpine

LABEL org.opencontainers.image.source="https://example.com/repo"

Build Args, Env, Workdir

ARG NODE_ENV=production
ENV NODE_ENV=$NODE_ENV \
    TZ=Europe/London

WORKDIR /app

Copy & Dependencies

# leverage layer caching: copy only manifests first
COPY package*.json ./
RUN npm ci --only=production

# then app source
COPY . .

Run Commands

# combine commands to reduce layers; use --no-cache prudently
RUN adduser -D appuser && chown -R appuser:appuser /app

Expose, User, Entrypoint/Cmd

EXPOSE 3000

USER appuser

# ENTRYPOINT defines the executable; CMD provides default args
ENTRYPOINT ["node", "server.js"]
# or:
# CMD ["node", "server.js"]

ENTRYPOINT vs CMD


Healthcheck (optional)

HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
  CMD wget -qO- http://localhost:3000/health || exit 1

Volumes (declare intended mount points)

VOLUME ["/data"]

Multi-Stage Build (example: Node build β†’ tiny runtime)

# build stage
FROM node:20-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# runtime stage
FROM node:20-alpine
WORKDIR /app
ENV NODE_ENV=production
COPY --from=build /app/dist ./dist
COPY package*.json ./
RUN npm ci --omit=dev
USER node
EXPOSE 3000
CMD ["node", "dist/index.js"]

.dockerignore (crucial for smaller, faster builds)

.git
node_modules
dist
*.log
.env
Dockerfile

Alpine APK & APT Best Practices

# Alpine
RUN apk add --no-cache curl

# Debian/Ubuntu
RUN apt-get update && apt-get install -y --no-install-recommends curl \
 && rm -rf /var/lib/apt/lists/*

Security & Size Tips


Build & Run (from this Dockerfile)

docker build -t myapp:latest .
docker run -d --name myapp -p 3000:3000 myapp:latest

If you want this tailored to your Raspberry Pi (ARM64) builds, I can add multi-arch notes (--platform linux/arm64) and a minimal compose file.