Build Pipeline¶
Optimize Docker builds and artifact creation.
Docker Build Basics¶
Dockerfile Best Practices¶
# Use specific version tags
FROM python:3.12-slim AS base
# Set environment
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PIP_NO_CACHE_DIR=1
# Create non-root user
RUN useradd -m -s /bin/bash app
WORKDIR /app
# Install dependencies first (better caching)
COPY requirements.txt .
RUN pip install -r requirements.txt
# Copy application code last
COPY --chown=app:app . .
# Switch to non-root user
USER app
# Run application
CMD ["python", "-m", "uvicorn", "app.main:app", "--host", "0.0.0.0"]
Multi-Stage Builds¶
# Build stage
FROM python:3.12-slim AS builder
WORKDIR /app
COPY requirements.txt .
# Build wheels
RUN pip wheel --no-cache-dir --wheel-dir /wheels -r requirements.txt
# Production stage
FROM python:3.12-slim AS production
# Copy only wheels
COPY --from=builder /wheels /wheels
RUN pip install --no-cache-dir /wheels/*
COPY . .
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0"]
Frontend Multi-Stage Build¶
# Build stage
FROM node:20-alpine AS builder
WORKDIR /app
COPY package.json bun.lock ./
RUN bun install --frozen-lockfile
COPY . .
RUN bun run build
# Production stage
FROM node:20-alpine AS production
WORKDIR /app
# Only copy built files and production dependencies
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public
ENV NODE_ENV=production
EXPOSE 3000
CMD ["node", "server.js"]
GitHub Actions Build¶
Basic Docker Build¶
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ghcr.io/${{ github.repository }}:${{ github.sha }}
With Layer Caching¶
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: |
ghcr.io/${{ github.repository }}:${{ github.sha }}
ghcr.io/${{ github.repository }}:latest
cache-from: type=gha
cache-to: type=gha,mode=max
Multi-Platform Builds¶
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build multi-platform
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: ghcr.io/${{ github.repository }}:latest
Tagging Strategy¶
Semantic Versioning¶
- name: Extract version
id: version
run: |
if [[ "${{ github.ref }}" == refs/tags/v* ]]; then
VERSION=${GITHUB_REF#refs/tags/v}
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "is_release=true" >> $GITHUB_OUTPUT
else
echo "version=${{ github.sha }}" >> $GITHUB_OUTPUT
echo "is_release=false" >> $GITHUB_OUTPUT
fi
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: |
ghcr.io/${{ github.repository }}:${{ steps.version.outputs.version }}
${{ steps.version.outputs.is_release == 'true' && format('ghcr.io/{0}:latest', github.repository) || '' }}
Auto-Tagging with Metadata¶
- name: Docker metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
Build Optimization¶
Dependency Caching¶
# Python
- name: Cache pip packages
uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements*.txt') }}
# Node.js with Bun
- name: Cache bun store
uses: actions/cache@v4
with:
path: ~/.bun/install/cache
key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lock') }}
Docker Build Cache¶
# Registry cache (recommended for teams)
- name: Build with registry cache
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: myapp:latest
cache-from: type=registry,ref=myapp:buildcache
cache-to: type=registry,ref=myapp:buildcache,mode=max
# GitHub Actions cache (simple setup)
- name: Build with GHA cache
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: myapp:latest
cache-from: type=gha
cache-to: type=gha,mode=max
Build Arguments and Secrets¶
- name: Build with args
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: myapp:latest
build-args: |
NODE_ENV=production
API_URL=${{ vars.API_URL }}
secrets: |
npm_token=${{ secrets.NPM_TOKEN }}
# In Dockerfile
ARG NODE_ENV=development
ENV NODE_ENV=$NODE_ENV
# Use secrets securely (not stored in image)
RUN --mount=type=secret,id=npm_token \
NPM_TOKEN=$(cat /run/secrets/npm_token) bun install
Security Scanning¶
Trivy Scanner¶
- name: Build image
uses: docker/build-push-action@v5
with:
context: .
load: true # Load to local Docker
tags: myapp:scan
- name: Scan with Trivy
uses: aquasecurity/trivy-action@master
with:
image-ref: myapp:scan
format: 'sarif'
output: 'trivy-results.sarif'
severity: 'CRITICAL,HIGH'
- name: Upload scan results
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: 'trivy-results.sarif'
Snyk Container Scan¶
- name: Snyk Container scan
uses: snyk/actions/docker@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
image: myapp:latest
args: --severity-threshold=high
Artifact Management¶
Upload Build Artifacts¶
- name: Build application
run: |
bun run build
tar -czf build.tar.gz dist/
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: build-${{ github.sha }}
path: build.tar.gz
retention-days: 7
Download in Deploy Job¶
deploy:
needs: build
runs-on: ubuntu-latest
steps:
- name: Download artifact
uses: actions/download-artifact@v4
with:
name: build-${{ github.sha }}
- name: Deploy
run: |
tar -xzf build.tar.gz
./deploy.sh dist/
Complete Build Workflow¶
name: Build and Push
on:
push:
branches: [main]
tags: ['v*']
pull_request:
branches: [main]
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
security-events: write
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Registry
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=sha
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Scan for vulnerabilities
if: github.event_name != 'pull_request'
uses: aquasecurity/trivy-action@master
with:
image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
format: 'sarif'
output: 'trivy-results.sarif'
- name: Upload scan results
if: github.event_name != 'pull_request'
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: 'trivy-results.sarif'
Monorepo Builds¶
Build Changed Services Only¶
jobs:
detect-changes:
runs-on: ubuntu-latest
outputs:
api: ${{ steps.changes.outputs.api }}
web: ${{ steps.changes.outputs.web }}
steps:
- uses: actions/checkout@v4
- uses: dorny/paths-filter@v2
id: changes
with:
filters: |
api:
- 'services/api/**'
web:
- 'apps/web/**'
build-api:
needs: detect-changes
if: needs.detect-changes.outputs.api == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: docker build -t api services/api
build-web:
needs: detect-changes
if: needs.detect-changes.outputs.web == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: docker build -t web apps/web
Best Practices Summary¶
| Practice | Benefit |
|---|---|
| Multi-stage builds | Smaller images |
| Layer caching | Faster builds |
| Non-root user | Security |
| Pin versions | Reproducibility |
| Scan images | Security |
| Use .dockerignore | Faster builds |
| Cache dependencies | Speed |
| Tag semantically | Traceability |