Building Docker images manually on your laptop works fine for personal projects. But in a professional team, every code change should automatically trigger a build, test run, and deployment. This is CI/CD — Continuous Integration and Continuous Deployment. Docker fits perfectly into CI/CD pipelines because containers make builds reproducible and deployments consistent.
The Docker CI/CD workflow looks like this: a developer pushes code to Git, the CI system (GitHub Actions, GitLab CI, Jenkins) automatically builds a Docker image, runs tests inside that image, pushes the image to a registry with a version tag, and optionally deploys it to staging or production. Every step is automated, reproducible, and auditable.
GitHub Actions is one of the most popular CI/CD platforms. Here is a complete workflow that builds your Docker image and pushes it to Docker Hub on every push to the main branch:
name: Build and Push Docker Image
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
env:
DOCKER_IMAGE: yourusername/my-app
jobs:
build-and-push:
runs-on: ubuntu-latest
steps:
# Checkout code
- name: Checkout repository
uses: actions/checkout@v4
# Set up Docker Buildx (needed for multi-platform builds)
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
# Login to Docker Hub
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
# Extract metadata for tagging
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.DOCKER_IMAGE }}
tags: |
type=ref,event=branch
type=sha,prefix=
type=semver,pattern={{version}}
# Build and push
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
Never put credentials directly in workflow files. Use GitHub repository secrets instead. Go to your repository Settings → Secrets and variables → Actions → New repository secret. Add DOCKERHUB_USERNAME and DOCKERHUB_TOKEN (use an access token, not your password).
# Run tests using Docker
- name: Run tests
run: |
docker build --target test -t my-app:test .
docker run --rm my-app:test pytest tests/ -v
# Or using docker compose
- name: Run integration tests
run: |
docker compose -f docker-compose.test.yml up --abort-on-container-exit
docker compose -f docker-compose.test.yml down
Docker builds without caching are slow. GitHub Actions provides a cache mechanism that stores Docker layers between runs. The cache-from and cache-to options in the build-push-action handle this automatically. For a typical Python project, caching reduces build time from 5+ minutes to under 1 minute after the first run.
With Apple Silicon Macs common in development teams, building images for both AMD64 (x86_64) and ARM64 is often required:
- name: Build multi-platform image
uses: docker/build-push-action@v5
with:
platforms: linux/amd64,linux/arm64
push: true
tags: yourusername/my-app:latest
- name: Deploy to production server
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
docker pull yourusername/my-app:${{ github.sha }}
docker stop my-app || true
docker rm my-app || true
docker run -d --name my-app -p 5000:5000 --restart unless-stopped yourusername/my-app:${{ github.sha }}
In Part 11, we will explore Docker Swarm — Docker's built-in container orchestration for running containers across multiple servers with load balancing and automatic failover.
GitHub Actions is one of the most widely used CI/CD platforms and has excellent Docker support. A typical workflow builds, tests, and pushes a Docker image on every push to main:
name: Build and Push Docker Image
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: |
myuser/myapp:latest
myuser/myapp:${{ github.sha }}
Store Docker Hub credentials as GitHub repository secrets, not in the workflow file. The github.sha context variable provides the commit hash for traceability — you can always identify exactly which code version a running container contains.
Before pushing an image, run tests against it in CI. Build the image, start it as a service in the CI job, wait for it to be healthy, then run your test suite against it. This validates not just that the code works but that it works in the containerized environment — catching issues like missing environment variables, missing files, or incorrect permissions that only manifest in the container context.
Set up a GitHub Actions workflow for a simple application: the workflow should trigger on push, build the Docker image, run a container from the built image and verify it starts correctly (using a health check or a curl request), and push the image to Docker Hub if tests pass. Store your Docker Hub credentials as GitHub secrets. Verify the workflow runs successfully by pushing a change and watching the Actions tab.
Cloud computing is a domain where deep intuition — the ability to make good architectural decisions quickly, to diagnose problems efficiently, and to anticipate how systems will behave under load — develops through accumulated hands-on experience. Every project you build on cloud infrastructure teaches you something that cannot be learned from documentation alone. The cost surprises, the permission errors, the networking debugging sessions, the performance investigations — these are not obstacles to learning, they are the learning. The engineers who have built genuinely deep cloud intuition have usually accumulated it through many projects over several years, not from any single course or certification. Start building things, make mistakes safely in learning environments, and accumulate that experience deliberately.