Building & Pushing Container Images¶
Prerequisites¶
- Docker installed locally, or
- Docker alternative (Podman, Buildkit, etc.)
- Registry credentials (Github Container Registry, Docker Hub, etc.)
Registry Setup¶
GitHub Container Registry (GHCR)¶
# Create a GitHub Personal Access Token (PAT) with read:packages, write:packages, delete:packages
echo $PAT | docker login ghcr.io -u username --password-stdin
docker tag myapp:latest ghcr.io/your-org/myapp:latest
docker push ghcr.io/your-org/myapp:latest
Docker Hub¶
docker login
docker tag myapp:latest yourusername/myapp:latest
docker push yourusername/myapp:latest
Building Your First Image¶
Simple Dockerfile¶
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
EXPOSE 5000
CMD ["python", "app.py"]
Build & Push¶
# Build
docker build -t ghcr.io/your-org/myapp:v1.0.0 .
# Push
docker push ghcr.io/your-org/myapp:v1.0.0
# Tag as latest
docker tag ghcr.io/your-org/myapp:v1.0.0 ghcr.io/your-org/myapp:latest
docker push ghcr.io/your-org/myapp:latest
Multi-Stage Builds¶
Reduce image size by building in stages:
# Stage 1: Build
FROM golang:1.21 AS builder
WORKDIR /src
COPY . .
RUN go build -o app
# Stage 2: Runtime
FROM alpine:latest
COPY --from=builder /src/app /app
EXPOSE 8000
CMD ["/app"]
Image Tagging Strategy¶
docker tag myapp:latest ghcr.io/org/myapp:v1.2.3
docker push ghcr.io/org/myapp:v1.2.3
COMMIT=$(git rev-parse --short HEAD)
docker tag myapp:latest ghcr.io/org/myapp:${COMMIT}
docker push ghcr.io/org/myapp:${COMMIT}
BRANCH=$(git rev-parse --abbrev-ref HEAD)
docker tag myapp:latest ghcr.io/org/myapp:${BRANCH}
docker push ghcr.io/org/myapp:${BRANCH}
Using in Kubernetes Manifests¶
Update your kustomization to reference the image:
containers:
- name: app
image: ghcr.io/your-org/myapp:v1.0.0
images:
- name: myapp
newName: ghcr.io/your-org/myapp
newTag: v1.0.0
CI/CD Automation¶
GitHub Actions Example¶
name: Build & Push Image
on:
push:
branches: [main]
pull_request:
branches: [main]
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}/myapp
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and Push
uses: docker/build-push-action@v5
with:
context: .
push: ${{ github.event_name == 'push' }}
tags: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
Best Practices¶
- ✅ Use specific base image versions (not
latest) - ✅ Run as non-root user
- ✅ Keep images small (multi-stage builds, minimal base images)
- ✅ Scan for vulnerabilities (Trivy, Snyk)
- ✅ Use semantic versioning for tags
- ✅ Don't check in secrets; use CI secrets
Troubleshooting¶
"unauthorized: authentication required"¶
# Re-authenticate to registry
docker login ghcr.io
"denied: permission_denied"¶
- Check token has
write:packagesscope - Verify image namespace/org matches your GitHub org
Large image size?¶
- Remove build artifacts in multi-stage Build
- Use
.dockerignoreto exclude unnecessary files - Start with
alpineordistrolessbase images