Skip to content

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:packages scope
  • Verify image namespace/org matches your GitHub org

Large image size?

  • Remove build artifacts in multi-stage Build
  • Use .dockerignore to exclude unnecessary files
  • Start with alpine or distroless base images

Next Steps