Docker Container Registry Tutorial
Overview
Docker Container Registry is a crucial component in modern DevOps workflows, enabling teams to store, manage, and distribute Docker images. This tutorial covers how to build Docker images, push them to container registries (Docker Hub, GitHub Container Registry), and integrate them with CI/CD pipelines.
Understanding the distinction between Git repositories and container registries is essential: Docker does not pull directly from GitHub repositories. Instead, docker-compose and docker pull load images from container registries like Docker Hub or GitHub Container Registry (GHCR). You must build your image and push it to a registry for distribution.
This tutorial provides a complete guide with practical examples for deploying a Java Quarkus runtime container, including CI/CD integration, registry pushing, and Docker Compose usage.
Understanding Container Registries
How Docker Compose Works with Registries
When you define a service in docker-compose.yml, you reference image names such as grafana/grafana:9.x. Docker pulls these images from a container registry, not directly from GitHub source repositories.
For example, when you specify image: ghcr.io/org/repo:latest, Docker performs the following:
- On
docker-compose upordocker-compose pull, the image is fetched from the GitHub Container Registry (GHCR) - The source comes from a container registry, not from the Git repository
This distinction is important because it means you must:
- Build your Docker image
- Push it to a registry
- Reference the registry location in your compose file
Prerequisites for Deploying a Quarkus Runtime Container
To successfully deploy your own Quarkus application as a containerized service, you'll need:
- Build artifact: A runnable JAR file (fat-jar) or native image produced by your build process
- Dockerfile: A multi-stage Dockerfile to efficiently build and package your application
- CI/CD Pipeline: Automated build process (e.g., GitHub Actions) that builds and pushes images to a registry
- Container Registry: Access to Docker Hub or GitHub Container Registry (GHCR)
- Docker Compose Configuration: A
docker-compose.ymlfile that references your registry image
Creating a Multi-Stage Dockerfile
A multi-stage Dockerfile separates the build environment from the runtime environment, resulting in smaller, more secure images. Below is an example for a Java Quarkus application using OpenJDK 17.
Create a file named Dockerfile in your repository root:
# --- Build stage (optional local build if you want single Docker build) ---
FROM maven:3.9.5-eclipse-temurin-17 AS build
WORKDIR /app
COPY pom.xml mvnw ./
COPY .mvn .mvn
COPY src ./src
RUN mvn -DskipTests package
# --- Runtime stage ---
FROM eclipse-temurin:17-jre-jammy
WORKDIR /deploy
# Quarkus produces runner.jar (adjust if different)
COPY --from=build /app/target/*-runner.jar /deploy/app.jar
EXPOSE 8080
CMD ["java","-jar","/deploy/app.jar"]
Understanding the Dockerfile
This Dockerfile uses two stages:
- Build Stage: Uses Maven and full JDK to compile and package your application
- Runtime Stage: Uses only the JRE with the compiled artifact, reducing image size
Note: If you configure Quarkus to produce a "fast-jar" or uber-jar format, adjust the COPY command path in the runtime stage accordingly.
Setting Up CI/CD with GitHub Actions
Automating your build and push process ensures consistency and saves time. GitHub Actions provides a powerful, integrated CI/CD solution.
Create .github/workflows/ci.yml in your repository:
name: CI Build and Push
on:
push:
branches: [ main ]
permissions:
contents: read
packages: write
id-token: write
jobs:
build-and-push:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Java
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: 17
- name: Build with Maven
run: mvn -DskipTests package
- name: Log in to GHCR
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push image
uses: docker/build-push-action@v4
with:
context: .
push: true
tags: |
ghcr.io/<ORG_OR_USER>/<REPO>:${{ github.sha }}
ghcr.io/<ORG_OR_USER>/<REPO>:latest
Configuring the Workflow
- Replace placeholders: Update
<ORG_OR_USER>with your GitHub username/organization and<REPO>with your repository name - Permissions: The workflow needs
packages: writepermission to push to GHCR - Authentication:
GITHUB_TOKENis automatically provided by GitHub Actions
Alternative: Using Docker Hub
To push to Docker Hub instead of GHCR, modify the login step:
- name: Log in to Docker Hub
uses: docker/login-action@v2
with:
registry: docker.io
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_TOKEN }}
You'll need to configure DOCKER_USERNAME and DOCKER_TOKEN in your repository secrets.
Configuring Docker Compose
Once your image is in the registry, you can reference it in your docker-compose.yml file:
version: '3.8'
services:
my-quarkus-app:
image: ghcr.io/<ORG_OR_USER>/<REPO>:latest
ports:
- "8080:8080"
restart: unless-stopped
environment:
- JAVA_OPTS=-Xmx512m
How It Works
When you run docker-compose up, Docker automatically:
- Checks if the image exists locally
- Pulls the image from the specified registry if not found
- Creates and starts the container
This makes deployment across different environments seamless.
Local Build and Push Workflow
For local development and testing, you can manually build and push images to the registry.
Step 1: Build the Image
docker build -t ghcr.io/<ORG>/<REPO>:v1.0.0 .
This creates a Docker image with the specified tag.
Step 2: Login to the Registry
echo $GHCR_PAT | docker login ghcr.io -u <USERNAME> --password-stdin
Replace $GHCR_PAT with your GitHub Personal Access Token. For Docker Hub, use:
docker login docker.io -u <USERNAME>
Step 3: Push the Image
docker push ghcr.io/<ORG>/<REPO>:v1.0.0
docker push ghcr.io/<ORG>/<REPO>:latest
This uploads your image to the registry, making it available for deployment.
Understanding Image Tags and Auto-Updates
The :latest Tag
The :latest tag is simply a conventional tag name — it doesn't automatically update when new commits are pushed to your repository. When you specify :latest in your Docker Compose file, Docker pulls whatever image is currently tagged as latest in the registry.
Implementing Auto-Updates
To enable automatic updates in production environments, you have several options:
-
Manual Update Commands:
docker-compose pull && docker-compose up -dThis pulls the latest images and recreates containers.
-
Watchtower: An automated solution that monitors your registry and updates containers when new images are available.
watchtower:
image: containrrr/watchtower
volumes:
- /var/run/docker.sock:/var/run/docker.sock
command: --interval 300 -
CI/CD Triggered Deployments: Configure your CI pipeline to trigger redeployment after successful image builds.
Deployment Checklist
Before deploying your containerized application, verify the following:
Build Configuration
- ✅ Build process produces a runnable JAR (Quarkus runner or fast-jar format)
- ✅ Dockerfile is properly configured with multi-stage build
- ✅ Runtime stage correctly copies the JAR artifact
CI/CD Pipeline
- ✅ GitHub Actions workflow is configured and tested
- ✅ Workflow successfully builds and pushes images to GHCR or Docker Hub
- ✅ Image tags include both version-specific and
:latesttags
Registry and Authentication
- ✅ Container registry is accessible (GHCR or Docker Hub)
- ✅ Registry credentials are configured (GHCR PAT or Docker Hub token)
- ✅ Repository permissions allow package publishing
Docker Compose
- ✅
docker-compose.ymlreferences correct registry image (ghcr.io/...ordocker.io/...) - ✅ Service configuration includes appropriate environment variables
- ✅ Port mappings and volume mounts are correct
Optional Enhancements
- ✅ Watchtower or similar tool configured for automatic updates
- ✅ Monitoring and logging solutions integrated
- ✅ Health checks defined in Dockerfile and compose file
Additional Resources
- GitHub Container Registry Documentation
- Docker Hub
- Quarkus Container Images Guide
- Watchtower Documentation
Best Practices
Image Tagging Strategy
- Use semantic versioning: Tag images with version numbers (e.g.,
v1.0.0,v1.0.1) - Maintain
:latest: Always push a:latesttag for development/testing - Git SHA tags: Include git commit SHA for traceability (e.g.,
${{ github.sha }})
Security Considerations
- Use minimal base images: Prefer JRE over JDK for runtime containers
- Multi-stage builds: Keep build dependencies out of production images
- Scan images: Use tools like Trivy or Snyk to scan for vulnerabilities
- Non-root users: Run containers as non-root when possible
Performance Optimization
- Layer caching: Order Dockerfile commands from least to most frequently changing
.dockerignore: Exclude unnecessary files from build context- Image size: Regularly audit and minimize image sizes
Next Steps
Now that you understand container registries and deployment workflows, consider:
- Customize your Dockerfile: Adapt the example to your specific Quarkus configuration (native images, fast-jar, etc.)
- Configure CI/CD: Set up GitHub Actions with your organization and repository details
- Integrate monitoring: Add Prometheus, Grafana, or other monitoring tools to your
docker-compose.yml - Implement health checks: Add health check endpoints to your application and Docker configuration
- Set up environments: Create separate workflows for development, staging, and production
Advanced Topics: Explore Kubernetes deployments, Helm charts, and container orchestration for more complex deployment scenarios.
Content Review
The content in this repository has been reviewed by chevp. Chevp is dedicated to ensuring that the information provided is accurate, relevant, and up-to-date, helping users to learn and implement programming skills effectively.
About the Reviewer
For more insights and contributions, visit chevp's GitHub profile: chevp's GitHub Profile.