Docker, Docker Hub, GitHub Packages, and CI/CD Pipelines: The Complete Picture
What Is Docker and Why Does It Matter?#
When you write code on your laptop, it works. When you deploy it to a server, it breaks. Different OS version, different library versions, different environment variables, different file paths. “It works on my machine” is one of the oldest problems in software.
Docker solves this by packaging your application with everything it needs to run — the OS libraries, the runtime, the config — into a single image. That image runs identically everywhere: your laptop, a CI server, a production server in a data center.
Container vs Image:
- Image — a read-only template. Like a class definition.
- Container — a running instance of an image. Like an object.
You build an image once, push it to a registry, and pull+run it anywhere.
Your First Dockerfile#
A Dockerfile defines how to build an image:
| |
This is a multi-stage build. The first stage (builder) uses the full Go image to compile the binary. The second stage (FROM alpine) starts fresh with a tiny base image and only copies the compiled binary. The final image is ~15MB instead of ~600MB.
Build and run:
| |
Docker Compose: Multiple Services Together#
Most applications need more than one container — a Go backend, ClickHouse, Redis. Docker Compose defines them together:
| |
| |
--wait waits until all health checks pass before returning. This means make docker-up && make run won’t fail because ClickHouse isn’t ready yet.
Docker Hub: The Public Registry#
Docker Hub is Docker’s default registry — where images are stored and shared. The official images you use (golang:1.24, redis:7-alpine, alpine:3.21) all come from Docker Hub.
Push your own image:
| |
Now anyone can run your application with:
| |
Docker Hub free tier: unlimited public repositories, 1 private repository. For private images, you either pay for Docker Hub Pro or use an alternative registry.
GitHub Container Registry: Docker Hub Alternative#
If your code is already on GitHub, GitHub Container Registry (GHCR) is the natural choice for private images. It’s integrated with GitHub permissions — your team’s GitHub access automatically extends to your private images.
| |
GHCR advantages over Docker Hub:
- Integrated with GitHub Actions — no separate credentials to manage
- Private packages included in all GitHub plans (free for public repos)
- Package visibility controlled by repository permissions
- Automatic cleanup of old images via retention policies
GHCR disadvantages:
- Tied to GitHub (Docker Hub is provider-independent)
- Rate limits on public pulls (same as Docker Hub, but less well-documented)
GitHub Actions: Full CI/CD Pipeline#
GitHub Actions runs automated workflows on every push, PR, or tag. Here’s a complete pipeline that:
- Runs tests on every push
- Builds and pushes a Docker image on every merge to main
- Builds a release image on every git tag
| |
Key details:
secrets.GITHUB_TOKEN — GitHub automatically provides this secret. You don’t need to create it. It has permissions to push to GHCR for the repository it’s running in.
cache-from: type=gha — caches Docker build layers in GitHub Actions cache. The next build reuses unchanged layers, making builds much faster (especially for the go mod download step).
docker/metadata-action — generates image tags automatically: latest for main branch, semver tags for git tags, SHA tags for every commit.
Deploying from CI/CD#
After the image is pushed, deployment depends on your infrastructure:
Simple: SSH + Docker pull
| |
DigitalOcean App Platform — connect your GitHub repo, point at the Dockerfile, and it automatically deploys on every push to main. Zero server management.
Kubernetes — update the image tag in your deployment manifest and apply. CI/CD tools like ArgoCD or Flux watch the manifest repo and apply changes automatically.
Docker Hub vs GHCR vs Other Registries#
| Registry | Free private | Auth | Best for |
|---|---|---|---|
| Docker Hub | 1 repo | Docker credentials | Public images, standard tooling |
| GHCR | Unlimited | GitHub token | GitHub-hosted projects |
| AWS ECR | Yes (500MB) | AWS IAM | AWS deployments |
| Google Artifact Registry | Yes | GCP service account | GCP deployments |
| Self-hosted (Registry v2) | Unlimited | Custom | Full control, on-premise |
For a project hosted on GitHub deploying to DigitalOcean or a VPS: GHCR is the simplest choice. No extra credentials, automatic permission inheritance from GitHub, free private repos.
For maximum portability (not tied to GitHub): Docker Hub with a paid account, or a self-hosted registry.
What Gets Cached and Why It Matters#
Docker builds layer-by-layer. Each RUN, COPY, and ADD instruction creates a layer. Layers are cached — if the instruction and its inputs haven’t changed, Docker reuses the cached layer.
This is why the Dockerfile copies go.mod and go.sum before copying the source:
| |
| |
Layer ordering is the single biggest impact on build speed. Always put things that change rarely (dependency files, base configuration) before things that change often (source code).