Docker and Kubernetes course menu
Build and Push Docker Images with GitHub Actions
Automate Docker builds in CI/CD: build on every commit, run tests in a container, push to GitHub Container Registry with commit tags, cache layers, scan images, and deploy.
On this page
In a real project, people do not build production images on their laptops. A CI pipeline builds, tests and publishes them automatically, so that each image can be traced to one commit.
The pipeline#
git push --> build image --> run tests --> scan --> push to registry --> deployA GitHub Actions workflow#
This workflow builds on every pull request, and pushes to GitHub Container Registry when code reaches main. It needs no extra secret, because GitHub provides GITHUB_TOKEN:
name: Docker
on:
push:
branches: [main]
pull_request:
env:
IMAGE: ghcr.io/${{ github.repository }}
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v7
- uses: docker/setup-buildx-action@v4
- name: Log in to the registry
if: github.event_name == 'push'
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v7
with:
context: .
push: ${{ github.event_name == 'push' }}
tags: |
${{ env.IMAGE }}:latest
${{ env.IMAGE }}:sha-${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=maxThe action versions change over time, so check each action's page for the latest one.
Testing in the build#
A neat way to run tests is as a stage in the Dockerfile. If the tests fail, the build fails:
FROM node:24-alpine AS test
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm test
FROM node:24-alpine
ENV NODE_ENV=production
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
USER node
CMD ["node", "server.js"]In CI, build the test stage first:
docker build --target test -t app:test .Testing with a database#
Compose can start the whole stack in CI:
- run: docker compose up -d --build --wait
- run: curl -f http://localhost:8000/health
- run: docker compose logs
if: failure()--wait blocks until services are healthy.
Scanning#
Add a scan step, so that serious vulnerabilities stop the pipeline. Trivy provides an action, aquasecurity/trivy-action, with an option to fail on CRITICAL and HIGH results.
Deploying#
There are many ways to deploy an image. A simple one for a single server is SSH:
ssh deploy@server "docker pull ghcr.io/your-name/app:sha-abc123 && docker compose up -d"Other platforms pull the image for you: Kubernetes, AWS ECS, Google Cloud Run, Azure Container Apps, and many hosting services.
Rolling back#
Because each image has a unique tag, rolling back means deploying the previous tag. That is why you should not deploy latest.
Common mistakes#
Practice#
Practice exercise
In the workflow above, what stops a pull request from a fork pushing an image?
Show solution
The push value is true only for push events, so pull requests only build the image. The login step is also skipped, using if: github.event_name == 'push'.
Key takeaway#
Next: What Is Kubernetes?.
Finished this lesson?
Related lessons
Recommended Docker and Kubernetes resources
- Docker Deep Dive by Nigel Poulton
A practical guide to how Docker works, for readers who have finished the basics.
Some links are affiliate links. If you buy through them, Laviyx Learn may earn a commission at no extra cost to you. As an Amazon Associate, we earn from qualifying purchases.