19.1.4.5 Push Pipeline Publishing
A focused guide to Push Pipeline Publishing, connecting core concepts with practical Docker and container operations.
Pipeline publishing refers to automating the process of building, tagging, and pushing Docker images as part of a CI/CD pipeline. Instead of manually running docker build and docker push from a developer workstation, the pipeline executes these steps automatically on every relevant code event — such as a commit to a specific branch, a merged pull request, or a tagged release. The result is a reproducible, auditable, and consistent publishing workflow that does not depend on any individual's local environment.
The Core Pipeline Sequence
A Docker image publishing pipeline typically follows this sequence:
Each stage is a shell command or a pipeline job that runs in a clean, isolated runner environment.
GitHub Actions Example
GitHub Actions is one of the most widely used CI platforms for Docker publishing. A workflow that builds and pushes on every push to main:
name: Build and Push Docker Image
on:
push:
branches:
- main
jobs:
build-and-push:
runs-on: ubuntu-latest
steps:
- name: Checkout source
uses: actions/checkout@v4
- name: Log in to Docker Hub
run: echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin
- name: Build image
run: docker build -t myapp:${{ github.sha }} .
- name: Tag as latest
run: docker tag myapp:${{ github.sha }} mydockerhubuser/myapp:latest
- name: Push versioned tag
run: docker push mydockerhubuser/myapp:${{ github.sha }}
- name: Push latest tag
run: docker push mydockerhubuser/myapp:latest
Secrets (DOCKER_USERNAME, DOCKER_PASSWORD) are stored in the repository's GitHub Secrets settings and injected as environment variables at runtime. They never appear in logs.
GitLab CI Example
GitLab CI provides built-in variables for its integrated registry:
stages:
- build
- push
build-image:
stage: build
script:
- docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
push-image:
stage: push
script:
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" "$CI_REGISTRY"
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
- docker tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA $CI_REGISTRY_IMAGE:latest
- docker push $CI_REGISTRY_IMAGE:latest
only:
- main
CI_REGISTRY, CI_REGISTRY_USER, CI_REGISTRY_PASSWORD, and CI_REGISTRY_IMAGE are all automatically populated by GitLab for every pipeline run.
Jenkins Pipeline Example (Declarative)
pipeline {
agent any
environment {
REGISTRY = 'myregistry.example.com'
IMAGE = "${REGISTRY}/myapp"
TAG = "${GIT_COMMIT}"
}
stages {
stage('Build') {
steps {
sh "docker build -t ${IMAGE}:${TAG} ."
}
}
stage('Push') {
steps {
withCredentials([usernamePassword(credentialsId: 'registry-creds', usernameVariable: 'USER', passwordVariable: 'PASS')]) {
sh "echo $PASS | docker login ${REGISTRY} -u $USER --password-stdin"
sh "docker push ${IMAGE}:${TAG}"
sh "docker tag ${IMAGE}:${TAG} ${IMAGE}:latest"
sh "docker push ${IMAGE}:latest"
}
}
}
}
}
Tag Strategy in Pipelines
Pipelines typically generate multiple tags per build to serve different consumers:
| Tag | Purpose |
|---|---|
$GIT_SHA | Exact traceability — immutable identifier |
$SEMVER (e.g. 1.4.2) | Release versioning for production deployments |
$BRANCH (e.g. main) | Latest build on a specific branch |
latest | Most recent stable release |
$DATE (e.g. 20240615) | Chronological rollback reference |
A pipeline that generates all of these:
VERSION="1.4.2"
GIT_SHA=$(git rev-parse --short HEAD)
BRANCH=$(git rev-parse --abbrev-ref HEAD)
DATE=$(date +%Y%m%d)
REGISTRY="myregistry.example.com/myapp"
docker build -t $REGISTRY:$VERSION .
docker tag $REGISTRY:$VERSION $REGISTRY:$GIT_SHA
docker tag $REGISTRY:$VERSION $REGISTRY:$BRANCH
docker tag $REGISTRY:$VERSION $REGISTRY:$DATE
docker tag $REGISTRY:$VERSION $REGISTRY:latest
docker push $REGISTRY:$VERSION
docker push $REGISTRY:$GIT_SHA
docker push $REGISTRY:$BRANCH
docker push $REGISTRY:$DATE
docker push $REGISTRY:latest
Conditional Publishing
Not every pipeline run should publish an image. Common conditions:
- Push only on the
mainorrelease/*branches. - Push only when tests pass.
- Push only when a version tag is created (e.g.,
v*pattern).
GitHub Actions conditional example:
- name: Push image
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
run: docker push mydockerhubuser/myapp:latest
Multi-Platform Builds in Pipelines
Pipelines can produce images for multiple CPU architectures using docker buildx:
docker buildx create --use
docker buildx build \
--platform linux/amd64,linux/arm64 \
--tag myregistry.example.com/myapp:1.0.0 \
--push \
.
The --push flag in buildx pushes directly to the registry as part of the build, bypassing the local image store.
Caching Layers Between Runs
Pipeline runners are stateless, so Docker build cache is lost between runs by default. Cache can be preserved using registry-backed caching:
docker buildx build \
--cache-from type=registry,ref=myregistry.example.com/myapp:cache \
--cache-to type=registry,ref=myregistry.example.com/myapp:cache,mode=max \
--tag myregistry.example.com/myapp:1.0.0 \
--push \
.
This significantly reduces build times for images with stable base layers.
Security Considerations
- Store registry credentials as encrypted secrets, never in code or plain environment variables.
- Use short-lived tokens (e.g., AWS ECR tokens expire after 12 hours) and refresh them before each pipeline run.
- Scope credentials to only the permissions needed (push to a specific repository, not all repositories).
- Scan images before publishing using tools integrated into the pipeline step between build and push.