GitHub Actions: Building and Pushing Docker Images to GHCR
What is GitHub Actions?
GitHub Actions is a built-in CI/CD (Continuous Integration / Continuous Delivery) platform provided by GitHub. It lets you automate your software workflows — such as building, testing, and deploying your application — directly from your GitHub repository.
Workflows are defined as YAML files placed inside the .github/workflows/ directory of your repository. GitHub automatically detects and runs them based on the events you configure.
Core Concepts
| Concept | Description |
|---|---|
| Workflow | The top-level automation file (.yml) that defines when and what to run |
| Event | The trigger that starts a workflow (e.g. push, pull_request) |
| Job | A group of steps that run on the same machine (runner) |
| Step | A single task inside a job — either a shell command (run) or a reusable action (uses) |
| Action | A pre-built, reusable unit of work published on the GitHub Marketplace |
| Runner | The virtual machine that executes the job (e.g. ubuntu-latest) |
| Secret | An encrypted variable stored in GitHub, injected into workflows at runtime |
What is an "Action"?
An action is a reusable component you reference with the uses keyword. Instead of writing shell scripts from scratch, you use community-maintained or official actions.
Actions are versioned (e.g. @v4). Always pin to a version to avoid unexpected breaking changes.
What is secrets.GITHUB_TOKEN?
GITHUB_TOKEN is an automatically generated secret created by GitHub at the start of every workflow run. You never need to create it manually.
It provides scoped permissions to interact with the GitHub API — in this case, pushing Docker images to GitHub Container Registry (GHCR).
Permissions are controlled per-job using the permissions block:
GitHub Container Registry (GHCR)
GHCR (ghcr.io) is GitHub's built-in Docker image registry. Images are stored under:
For example: ghcr.io/Fcakiroglu16/example:latest
It is tightly integrated with GitHub — package visibility follows repository visibility, and authentication uses the same GITHUB_TOKEN.
Docker Image Tagging Strategy
Tags identify different versions of the same image. Using multiple tags is a best practice:
| Tag Type | Example | Purpose |
|---|---|---|
latest | ghcr.io/owner/repo:latest | Always points to the most recent build |
| Git SHA | ghcr.io/owner/repo:sha-abc1234 | Exact traceability — which commit built this image |
| Date | ghcr.io/owner/repo:20260505 | Chronological grouping by build date |
The docker/metadata-action@v5 action generates these tags automatically based on the Git context.
OCI Labels
OCI (Open Container Initiative) labels are metadata embedded inside the Docker image. They follow a standard format:
metadata-action generates these automatically from your GitHub repo context. GHCR reads them to display the linked repository, creation time, and commit on the package page.
Workflow Breakdown
The workflow runs in 8 sequential steps inside a single job:
Tests must pass before the Docker image is built. If any test fails, the workflow stops and no image is pushed.
Full Workflow Code
Step-by-Step Explanation
Step 1 — actions/checkout@v4
Clones the repository onto the runner. Without this step, none of the source files would be available.
Step 2 — actions/setup-dotnet@v4
Installs the specified .NET SDK version on the runner. The dotnet-version: '10.0.x' wildcard installs the latest patch of .NET 10.
Step 3 — dotnet restore
Downloads all NuGet packages declared in the .csproj files. This is a prerequisite for building.
Step 4 — dotnet build --no-restore
Compiles the solution. --no-restore skips a redundant restore since Step 3 already did it.
Step 5 — dotnet test --no-build --verbosity normal
Runs all test projects found in the solution. If any test fails, the job fails here and the Docker image is never pushed.
Step 6 — docker/login-action@v3
Authenticates to GHCR using the auto-generated GITHUB_TOKEN. Required before any docker push operation.
Step 7 — docker/metadata-action@v5
Generates image tags and OCI labels from the current Git context. The output is stored in steps.meta.outputs.tags and steps.meta.outputs.labels for use in the next step.
Step 8 — docker/build-push-action@v6
Builds the Docker image using the Dockerfile at the specified path, then pushes it to GHCR. The push condition ensures images are only pushed on actual push events — not on pull requests.