Skip to content

Dockerfile Support

When a Dockerfile is present at the repository root, the PaaS build planner automatically routes your build through the dedicated paas-dockerfile-pipeline instead of the Paketo buildpack pipeline.

Detection flow

sequenceDiagram
  participant D as Developer
  participant CP as Control Plane
  participant LD as language_detector
  participant TK as Tekton (paas-build)
  participant K as Kaniko
  participant H as Harbor
  participant T as Trivy

  D->>CP: POST /v1/apps/{id}/builds
  CP->>LD: detect_language(repo)
  LD-->>CP: DetectedBuildpack::Dockerfile (priority 1)
  CP->>TK: PipelineRun (paas-dockerfile-pipeline)
  TK->>TK: clone (git-clone Task)
  TK->>K: kaniko-build (Dockerfile context)
  K->>H: push image
  TK->>T: trivy-scan --severity CRITICAL --exit-code 1
  T-->>TK: pass / fail
  TK-->>CP: image URL or build error

Dockerfile always wins over buildpack indicator files (package.json, go.mod, Cargo.toml, etc.) — you don't need a paas.toml override to opt out of buildpacks.

When to use a Dockerfile

  • Official images that already ship one: n8n, vLLM, Metabase, Plausible, Mattermost, …
  • Stacks not covered by the eight Paketo buildpacks (see Buildpacks Detection)
  • Custom base image or specific OS packages
  • Optimised multi-stage builds (compile-time deps in stage 1, runtime image in stage 2)

Multi-stage builds

Kaniko supports multi-stage Dockerfiles natively — no extra config:

FROM node:20 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html

Build args

Pass build-time variables via paas.toml:

[build]
build_args = { NODE_ENV = "production", API_URL = "https://api.example.com" }

The control plane forwards these as --build-arg KEY=VAL flags to Kaniko. Empty values are allowed; values containing spaces are not (current build-args-str parameter is space-separated). Use a comma-separated env file or escape your value if you need spaces.

Security

  • Trivy scan runs after every build with --severity CRITICAL --exit-code 1. Builds with at least one CRITICAL CVE fail before the image is tagged latest in Harbor — the pipeline never marks the build successful.
  • Image size warnings >1GB and refusal >5GB: Phase 2.
  • USER root warning at build-time: Phase 2.

Troubleshooting

  • apt-get 404: combine apt-get update && apt-get install -y … in a single RUN so the metadata cache is fresh in the same layer.
  • FROM scratch: the binary must be statically linked — no glibc, no dynamic loader. Use musl for Rust/Go.
  • Multi-stage failure: stage names (after AS …) must be unique within a single Dockerfile.
  • Trivy fails with CRITICAL: check the exact CVE id in the build log, upgrade the affected package or pin a base image with the fix.

Security warnings

USER root warning

Trivy scans each built image for security configuration issues. When the final stage of a Dockerfile runs as root (i.e. no USER directive, or USER root explicitly), Trivy emits a MEDIUM-severity finding in the post-build scan.

Recommendation — end every Dockerfile with a non-root user:

RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser

A future Phase 2 update will surface USER-root warnings directly in the PaaS dashboard before the image is pushed, so tenants receive actionable feedback earlier in the build loop instead of having to read the Trivy log after the fact.

Image size limits

Phase 2 — a post-kaniko Tekton step will inspect the pushed image manifest (via skopeo inspect --raw or crane manifest) and apply the following policies:

Image size Action
< 1 GB None
1 GB – 5 GB Warning visible in dashboard
> 5 GB Build refused (exit 1) — cluster limit

Currently kaniko builds without any size constraint. To keep your images small until Phase 2 ships, the established practices are:

  • Use multi-stage builds (heavy compile-time deps in stage 1, only the runtime artifacts copied into stage 2)
  • Choose minimal base images: alpine, distroless, scratch
  • Avoid apt-get in the final stage; install only the runtime deps
  • Run docker history on a built image locally to spot the layers that bloat your final size