Cubis Engineers

Images and Builds

Build a small non-root image, use the cache deliberately, and publish an identifiable artifact.

Application deliveryFoundationUpdated Aug 13, 2026dockerdockerfilebuildkitimagessupply-chain

A Dockerfile is a build recipe. Each instruction contributes to the image and can affect cache reuse, security, and reproducibility.

Keep the build context small

.dockerignore
.git
.env*
node_modules
npm-debug.log*
coverage
dist

The build context is the set of files available to COPY and ADD. Exclude local dependencies, Git history, build output, and secrets before they reach the builder.

Separate build and runtime

Dockerfile
FROM node:lts-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build && npm prune --omit=dev

FROM node:lts-alpine AS runtime
ENV NODE_ENV=production
WORKDIR /app
USER node
COPY --chown=node:node --from=build /app ./
EXPOSE 3000
CMD ["node", "server.js"]

The build stage contains compilers and development dependencies. The runtime stage receives only the files needed to start the service. USER node prevents the application process from running as root inside the container.

For production, replace floating base tags with an approved version or digest and rebuild regularly for operating-system and runtime updates. A digest fixes the exact content; a tag alone can later point to different content.

Arrange instructions for useful caching

Dependency files are copied before application source so a source-only change can reuse the dependency layer. Validate the behavior rather than assuming the cache was used:

Terminal
docker build --pull --tag cubis-api:dev .
docker image inspect cubis-api:dev
docker history --no-trunc cubis-api:dev

--pull checks for a newer base image. It does not make the build reproducible by itself; dependency lock files and pinned inputs still matter.

Do not pass secrets as build arguments

Build arguments and environment variables can remain in image metadata or layers. Use BuildKit secret mounts for credentials needed only by one build step:

Dockerfile
RUN --mount=type=secret,id=npmrc,target=/root/.npmrc \
    npm ci
Terminal
docker build --secret id=npmrc,src="$HOME/.npmrc" -t cubis-api:dev .

The secret is mounted for that instruction and is not copied into the resulting layer. Ensure the build command does not print it to logs.

Test the artifact you will publish

Terminal
docker run --rm --read-only --tmpfs /tmp \
  -p 127.0.0.1:3000:3000 \
  cubis-api:dev

Run unit and integration tests against the built image in CI. After publishing, record the registry digest alongside the source commit and deployment record.

Terminal
docker push registry.example.com/cubis-api:2026.08.13-3f28c1a
docker image inspect \
  registry.example.com/cubis-api:2026.08.13-3f28c1a \
  --format '{{index .RepoDigests 0}}'

References

On this page