Creating Deployment Artifacts from Docker Builds
AI generated
Docker · Build · CI/CD · Artifacts · BuildKit
Creating Deployment Artifacts from Docker Builds
Multi-stage builds, BuildKit --output and reproducible releases

Docker is not just a runtime, it is a portable build environment. Teams that create deployment artifacts from Docker builds get reproducible binaries, static assets and archives without installing the build toolchain on the CI server or the developer machine.

13 min read Multi-stage build · BuildKit --output · docker cp · SBOM · cache mounts Docker 24+ · BuildKit 0.12+ · GitHub Actions · GitLab CI

1. Docker as a build environment: the core idea

The core promise of Docker as a build environment is this: if the build runs inside a container, the result is identical on every machine, regardless of which version of PHP, Node, Go or Rust happens to be installed on the CI server or the development machine. Creating deployment artifacts from Docker builds means treating the Dockerfile not merely as a recipe for a runtime image, but as a complete, versioned build environment that takes input (source code) and produces output (compiled binaries, archives, static assets).

This approach solves the classic "it works on my machine" problem. Teams that create deployment artifacts from Docker builds get the same toolchain version in development, CI and production. The build environment is declared in the Dockerfile, versioned and reproducible. A Go binary compiled inside a golang:1.22 container is bit identical whether the build runs on a developer's laptop, on a GitHub Actions runner, or on a Kubernetes node.

2. Multi-stage Dockerfiles for clean artifact separation

Multi-stage builds are the cleanest way to create deployment artifacts from Docker builds while keeping the final image small. Every FROM statement starts a new build stage. With COPY --from=build-stage /app/dist ./dist, only the finished artifacts are copied into the next stage, while the entire build context with compiler, test dependencies and temporary files stays in the intermediate layer and never reaches the final image. This matters not only for image size but also for security: development dependencies with known CVEs are not present in the production image.

An important technique when producing deployment artifacts from Docker builds is targeting individual stages with docker build --target artifact-stage. This allows a single build to have several artifact stages, one for the compiled binary, one for static assets, one for test results, and the CI pipeline calls the stage it needs. With BuildKit, independent stages run in parallel, which noticeably reduces build time in multi-stage scenarios.


# syntax=docker/dockerfile:1.7
# Multi-stage Dockerfile: build PHP app + compile Node assets + export artifacts

# --- Stage 1: PHP dependency build ---
FROM composer:2.7 AS php-deps
WORKDIR /app
COPY composer.json composer.lock ./
# Cache mount: Composer cache survives across builds
RUN --mount=type=cache,target=/root/.composer \
    composer install --no-dev --optimize-autoloader --no-interaction

# --- Stage 2: Node asset build ---
FROM node:22-alpine AS node-assets
WORKDIR /build
COPY package.json package-lock.json ./
RUN --mount=type=cache,target=/root/.npm \
    npm ci --prefer-offline
COPY web/tailwind/ ./web/tailwind/
RUN npm run build

# --- Stage 3: Artifact collector (lightweight) ---
FROM scratch AS artifacts
COPY --from=php-deps /app/vendor ./vendor/
COPY --from=node-assets /build/web/css ./web/css/
COPY --from=node-assets /build/web/js  ./web/js/

# --- Stage 4: Final runtime image ---
FROM php:8.4-fpm-alpine AS runtime
COPY --from=php-deps /app/vendor /app/vendor
COPY --from=node-assets /build/web /app/web
COPY src/ /app/src/

3. Extracting artifacts from the container with docker cp

The classic way to extract deployment artifacts from Docker builds is a combination of docker build, docker create and docker cp. After the build, docker create creates a container from the image without starting it, which makes the container layer available and enables file access. Then docker cp copies the desired artifacts to the host filesystem. Finally, docker rm removes the temporary container. This method works with any Docker client without BuildKit and is therefore still common in older CI environments.

The downside of this method is that it requires three separate commands and can leave a dangling container behind on failure if the cleanup script is not properly guarded. The pattern trap "docker rm -f $CONTAINER_ID" EXIT in a shell script ensures the temporary container is cleaned up even when something fails. For deployment artifacts from Docker builds in modern environments with BuildKit, the --output method is more elegant.


#!/usr/bin/env bash
# extract-artifacts.sh: build and extract deployment artifacts from Docker
set -euo pipefail

IMAGE_TAG="my-app-build:$(git rev-parse --short HEAD)"
ARTIFACT_DIR="./dist"

# Build the image targeting the artifact stage
docker build \
  --target artifacts \
  --tag "$IMAGE_TAG" \
  --build-arg BUILD_ENV=production \
  .

# Create a temporary container (not started) to access the filesystem
CONTAINER_ID=$(docker create "$IMAGE_TAG")
trap "docker rm -f '$CONTAINER_ID' >/dev/null 2>&1" EXIT

mkdir -p "$ARTIFACT_DIR"

# Copy artifacts from container to host
docker cp "${CONTAINER_ID}:/vendor"  "${ARTIFACT_DIR}/vendor"
docker cp "${CONTAINER_ID}:/web/css" "${ARTIFACT_DIR}/web/css"
docker cp "${CONTAINER_ID}:/web/js"  "${ARTIFACT_DIR}/web/js"

echo "Artifacts extracted to ${ARTIFACT_DIR}"
ls -lh "${ARTIFACT_DIR}"

# Remove intermediate image to free disk space
docker rmi "$IMAGE_TAG"

4. BuildKit --output: writing directly to the host filesystem

BuildKit's --output flag offers a more direct alternative to the docker cp workflow. With docker buildx build --output type=local,dest=./dist ., files from the build context are exported straight into the given host directory, with no intermediate container involved. This is especially convenient for deployment artifacts from Docker builds that need to land directly in an artifact store such as GitHub Artifacts, S3 or Nexus. Combined with a FROM scratch AS artifacts stage, the exported directory contains exactly the files that stage holds.

The tar output type exports a TAR archive instead of a directory: --output type=tar,dest=./app.tar. This is ideal for deployment artifacts from Docker builds that need to be stored or transferred as versioned archives. With the image type and push=true, the image is pushed straight to a registry without being loaded locally, which saves disk I/O and suits CI environments with limited local storage.

5. BuildKit cache mounts for fast builds

BuildKit cache mounts (RUN --mount=type=cache,target=/root/.composer) are the single most important performance feature for deployment artifacts from Docker builds in CI/CD pipelines. They persist the download cache of package managers, Composer, NPM, pip, the Go module cache, apt packages, between builds without writing it into the image layer. In practice that means: on a second build with the same dependencies, no packages are downloaded again, even though the layer cache is invalidated by source code changes.

The difference from a regular layer cache is important: a layer cache is invalidated as soon as any input changes. A cache mount survives regardless. If only a single PHP class changes, the Composer layer cache becomes invalid, but the cache mount still holds every downloaded package, so composer install finishes in seconds instead of minutes. For deployment artifacts from Docker builds in GitHub Actions or GitLab CI, cache mounts can be persisted through the GitHub Actions cache with docker buildx build --cache-from type=gha --cache-to type=gha.

6. Producing PHP Composer and Node asset artifacts

For PHP projects built on Magento or Symfony, two kinds of deployment artifacts from Docker builds matter most: the optimized Composer vendor folder and the compiled frontend assets. Generating the vendor folder with composer install --no-dev --optimize-autoloader inside a Composer container and extracting it as an artifact means the deployment process on the target server no longer needs a Composer invocation at all. That removes a dependency from the production server and makes the deployment more deterministic.

Node assets, Tailwind CSS, JavaScript bundles, are typically compiled in a Node stage using the appropriate builder. The result is a dist/ or pub/static/ directory that is copied to the production server as a deployment artifact from Docker builds or uploaded to a CDN. By encapsulating the build inside Docker, teams can guarantee that exactly the same Node version and the same package versions are used for every build, which removes a common source of errors in manual asset builds.


# BuildKit --output: write artifacts directly to host filesystem (no docker cp needed)
docker buildx build \
  --target artifacts \
  --output type=local,dest=./dist \
  --build-arg BUILD_DATE="$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
  --build-arg GIT_SHA="$(git rev-parse --short HEAD)" \
  .

# Export as TAR archive for versioned storage
docker buildx build \
  --target artifacts \
  --output "type=tar,dest=./releases/app-$(git rev-parse --short HEAD).tar" \
  .

# Build and push image directly to registry without local storage
docker buildx build \
  --platform linux/amd64,linux/arm64 \
  --output type=image,push=true \
  --tag registry.example.com/my-app:$(git rev-parse --short HEAD) \
  .

# Use GitHub Actions cache backend for persistent cache across CI runs
docker buildx build \
  --cache-from type=gha \
  --cache-to type=gha,mode=max \
  --output type=local,dest=./dist \
  .

7. Exporting SBOM and provenance from Docker builds

A Software Bill of Materials (SBOM) and build provenance are increasingly relevant deployment artifacts from Docker builds that address compliance and security requirements. Docker BuildKit 0.11+ can automatically generate an SBOM as an image attestation: docker buildx build --sbom=true --provenance=true. The SBOM lists every package and dependency in the image and is pushed to the registry alongside the image as a signable OCI attestation manifest. docker buildx imagetools inspect image:tag displays the attestation.

For deployment artifacts from Docker builds in regulated environments, combining SBOM and provenance is especially valuable: provenance documents which source commit, which CI job and which BuildKit version produced the artifact. That makes the entire build path traceable, a requirement increasingly demanded by EU cybersecurity regulation (the CRA) and supply chain security frameworks such as SLSA.

8. Integrating artifact builds into GitHub Actions and GitLab CI

In GitHub Actions and GitLab CI, deployment artifacts from Docker builds are typically produced and stored in two steps: build and upload. GitHub Actions offers the actions/upload-artifact action, which stores arbitrary files as build artifacts and hands them off via download or between jobs. Combined with docker buildx build --output type=local,dest=./dist, the files land in the dist/ directory after the build and can be uploaded right away. The artifacts are then downloadable in the GitHub Actions UI and available to subsequent jobs via actions/download-artifact.

GitLab CI uses the artifacts directive in .gitlab-ci.yml. With artifacts: paths: [dist/] and expire_in: 7 days, the resulting deployment artifacts from Docker builds are stored after the job runs and passed on to subsequent jobs in the pipeline. That enables the typical CI/CD flow: the build job produces artifacts, the test job checks them, the deploy job ships them to the production server, all without every job repeating the full build.

9. Comparing artifact extraction methods

The different methods of extracting deployment artifacts from Docker builds come with different trade offs depending on the CI environment and the requirements at hand.

Method Requirement Advantage Drawback
docker cp Standard Docker Universally compatible 3 commands, container cleanup needed
BuildKit --output local BuildKit enabled Direct export, no container Requires BuildKit (default since Docker 23)
BuildKit --output tar BuildKit enabled Versioned archive usable right away Needs unpacking at deploy time
Registry push and pull Registry access Versioning and caching built in Requires registry infrastructure
SBOM + provenance BuildKit 0.11+ Compliance and supply chain security Requires an OCI-compatible registry

For modern CI/CD pipelines, BuildKit --output local combined with a downstream upload-artifact action is the recommended way to produce deployment artifacts from Docker builds. It combines the reproducibility of a Docker build with the simplicity of a direct file export and needs no registry infrastructure. For images that get deployed, a registry push remains the standard.

Mironsoft

CI/CD pipelines, Docker build optimization and deployment automation

Need reproducible deployment artifacts from Docker builds?

We build CI/CD pipelines that combine clean multi-stage Dockerfiles with BuildKit cache mounts and automated artifact export, for deployments that behave the same way every time.

Build optimization

Multi-stage Dockerfiles and cache mounts for fast, reproducible builds

CI/CD integration

Configuring GitHub Actions and GitLab CI with artifact upload and cache backend

SBOM & compliance

Automatic SBOM generation and provenance attestation for security requirements

10. Summary

Creating deployment artifacts from Docker builds is the safest way to ensure reproducible, toolchain-independent builds in CI/CD pipelines. Multi-stage Dockerfiles cleanly separate build and runtime context and keep the final image size minimal. BuildKit cache mounts eliminate redundant package downloads and speed up iterative builds considerably. BuildKit's --output method is the most modern way to write artifacts directly to the host filesystem, skipping the detour through container creation and docker cp.

For PHP projects that means, in practice: generate the Composer vendor folder inside a Composer container, compile Node assets inside a Node container, extract both artifacts and ship them to the production server, all without a local PHP or Node installation. SBOM and provenance as additional deployment artifacts from Docker builds satisfy growing compliance requirements and make the entire build path traceable.

Deployment Artifacts from Docker Builds: The Key Points at a Glance

Multi-stage builds

Build environment in early stages, artifacts in later stages. FROM scratch AS artifacts for a minimal export.

BuildKit --output

--output type=local,dest=./dist exports directly. No docker cp, no temporary container needed.

Cache mounts

RUN --mount=type=cache,target=/root/.composer persists the package cache beyond layer cache invalidations.

SBOM & provenance

--sbom=true --provenance=true on BuildKit 0.11+ for supply chain security and compliance documentation.

11. FAQ: Deployment Artifacts from Docker Builds

1What is a deployment artifact from a Docker build?
A concrete output file of a build: a binary, TAR archive, vendor folder, or CSS/JS assets, produced in an isolated, reproducible Docker environment.
2docker cp vs. BuildKit --output?
--output type=local writes directly to the host directory in a single step with no temporary container. docker cp needs build plus create plus cp plus rm.
3How do BuildKit cache mounts work?
RUN --mount=type=cache persists the path across builds without an image layer. The package cache survives even when the layer cache is invalidated.
4Target a single stage?
docker build --target stagename builds only up to the named stage. Ideal for separate artifact, test and runtime builds from one Dockerfile.
5Integrate artifacts into GitHub Actions?
--output type=local,dest=./dist, then actions/upload-artifact. Cache: --cache-from type=gha --cache-to type=gha.
6What is an SBOM?
Software Bill of Materials: a list of every package in the image. Generated automatically as an OCI attestation with --sbom=true. Used for compliance and security audits.
7Keep build tools out of the production image?
Multi-stage: compilers and tools live in early stages, COPY --from=build brings only artifacts into the final image. The build context never reaches the runtime image.
8Do stages run in parallel?
With BuildKit, yes. Independent stages are executed in parallel automatically, a PHP build and a Node build running at the same time.
9Multi-platform artifacts?
docker buildx build --platform linux/amd64,linux/arm64. With --output type=local, artifacts are separated by platform.
10What is build provenance?
A signed record of the build process: commit, CI job, BuildKit version. Generated automatically with --provenance=true. Relevant for SLSA compliance.