how to push docker image to docker hub?

    graph TD
        A[Docker Tagging Strategies] --> B{Choose a Tagging Strategy};
    
        B --> C[Unique / Immutable Tags];
        C --> C1[Specific Version: my-app:1.2.3];
        C --> C2[Git SHA: my-app:abc1234];
        C --> C3[Build ID: my-app:build-42];
        C --> C4[Digest: my-app@sha256:d1g3st];
        C -- Recommended for Production --> D{Benefits};
        D --> D1[Reproducibility];
        D --> D2[Traceability];
        D --> D3[Security];
    
        B --> E[Rolling / Mutable Tags];
        E --> E1[Major/Minor Version: my-app:2.1];
        E1 -- Receives Patch Updates --> E2[my-app:2.1.3];
        E --> E3[Major Version: my-app:2];
        E3 -- Receives Minor & Patch Updates --> E1;
        E -- Use with Caution --> F{Risks};
        F --> F1[Inconsistent Builds];
        F --> F2[Unexpected Updates];
    
        B --> G[<font color=red>The latest Tag</font>];
        G -- Avoid in Production --> H{Issues};
        H --> H1[<font color=red>Not a 'Latest' Release</font>];
        H --> H2[<font color=red>Unpredictable</font>];
        H --> H3[<font color=red>Hard to Rollback</font>];

    Login to docker via command line

    docker login
    Bash

    Common tagging strategies

    Semantic Versioning (SemVer)
    This is a recommended and widely adopted approach for stable releases that uses a three-part system: MAJOR.MINOR.PATCH

    • MAJOR version: For incompatible API changes.
    • MINOR version: For new, backward-compatible features.
    • PATCH version: For backward-compatible bug fixes.
    • Example: my-app:2.1.3 points to a very specific image version, while my-app:2.1 would automatically receive patch updates and my-app:2 would receive both minor and patch updates. 

    Unique/Immutable tags
    These tags change with every new image pushed, ensuring that a specific tag always points to the exact same image content. 

    • Git Commit SHA: Tags the image with the short hash of the Git commit used to build it (e.g., my-app:abc1234). This provides strong traceability between source code and image.
    • Build ID: Uses an incremental ID from a CI/CD pipeline (e.g., my-app:build-42) to correlate the image with a specific build job.
    • Manifest Digest: The most reliable but least human-readable option is to use the unique, content-addressable SHA-256 digest of the image manifest (e.g., my-app@sha256:...). This guarantees you are using the exact same image regardless of tag changes. 

    Rolling/Non-unique tags
    These are tags that are designed to be overwritten, or “floated,” to point to the newest stable version within a category. 

    • latest: By default, Docker assigns this tag if none is specified. It is mutable, and its meaning depends entirely on the image publisher. This tag should be avoided in production deployments to prevent unexpected updates and ensure reproducible builds.
    • Major/Minor versions: A tag like my-app:2 or my-app:2.1 is “stable” for users who want to receive the latest updates within a specific version branch. 

    Best practices for tagging

    • Be specific in production: Use immutable tags like SemVer with the full MAJOR.MINOR.PATCH or a Git SHA for production deployments. This prevents unforeseen changes if a rolling tag is updated.
    • Automate your process: Integrate tagging into your CI/CD pipeline to automatically apply tags based on version numbers, Git commits, or build IDs. This ensures consistency and accuracy.
    • Incorporate context: Add extra information to your tags to indicate the base image, environment, or architecture (e.g., my-app:1.2.3-alpinemy-app:1.2.3-prod).
    • Use multiple tags for flexibility: Assign multiple tags to an image. For instance, a single production image could have the tags my-app:1.2.3my-app:1.2, and my-app:latest

    docker tag command syntax

    To add a new tag to an existing image, use the following command:

    docker tag SOURCE_IMAGE[:TAG] TARGET_IMAGE[:TAG]
    Bash

    Example:
    To create a latest tag for a production build:

    docker tag my-app:1.0.0 my-app:latest
    Bash

    To tag a Docker image with both a specific version and the latest tag, and then push it to Docker Hub, follow these steps:

    • Build your Docker image (if you haven’t already):
    docker build -t your_username/your_image_name:specific_version .
    Bash

    Replace your_usernameyour_image_name, and specific_version with your actual details. The . indicates the current directory as the build context.

    • Tag the image with the latest tag:
    docker tag your_username/your_image_name:specific_version your_username/your_image_name:latest
    Bash

    This command creates an additional tag (latest) for the same image ID as specific_version. Log in to Docker Hub.

    docker login
    Bash

    Enter your Docker Hub username and password when prompted. Push both tags to Docker Hub.

    docker push your_username/your_image_name:specific_version
    docker push your_username/your_image_name:latest
    Bash

    Alternatively, to push all tags associated with an image, you can use the --all-tags flag:

    docker push --all-tags your_username/your_image_name
    Bash

    After these steps, your Docker image will be available on Docker Hub with both the specific_version tag and the latest tag. When a new version is released, you can repeat the process, tagging the new image with its new specific version and the latest tag, effectively updating the latest tag to point to the newest version.


    working example

    Create out sample FastAPI endpoint main.py

    from fastapi import FastAPI
    
    app = FastAPI()
    
    @app.get("/")
    async def read_root():
        return {"message": "Hello World"}
    Python

    Create requirements.txt file with require python packages

    fastapi
    uvicorn
    INI

    Now we will create our Docker file, typically named Dockerfile

    But in our case, we will be creating two versions, i.e., dev and prod

    which means “development” Docker image and a “production” Docker image

    First Development Docker file creation Dockerfile.dev

    # Use the official Python 3.11 Alpine base image.
    # This keeps the image size very small.
    FROM python:3.13-alpine
    
    # Install dumb-init using the Alpine Package Manager.
    RUN apk add --no-cache dumb-init
    
    # Set the working directory inside the container.
    WORKDIR /app
    
    # Copy the requirements file and install dependencies first.
    # This improves build speed by leveraging Docker's caching.
    COPY requirements.txt main.py .
    RUN pip install --no-cache-dir -r requirements.txt --break-system-packages
    
    # Expose the port that Uvicorn will run on.
    EXPOSE 8000
    
    # Define the command to run your application with Uvicorn.
    # The `host="0.0.0.0"` setting is essential for exposing the port
    # to your local machine or other network services.
    
    ENTRYPOINT ["dumb-init", "--"]
    CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
    Dockerfile

    Next we will create production Docker file named Dockerfile.prod

    # syntax=docker/dockerfile:1.4
    #
    # ====================================================================
    # STAGE 1: Build a virtual environment with production dependencies
    # ====================================================================
    FROM python:3.13-alpine as builder
    
    # Set the working directory inside the container
    WORKDIR /app
    
    # Install build dependencies and create a dedicated non-root user
    RUN apk add --no-cache dumb-init \
        && adduser --disabled-password --no-create-home appuser
    
    # Copy requirements file and install dependencies into a virtual environment
    COPY requirements.txt .
    RUN python -m venv /venv && /venv/bin/pip install --no-cache-dir -r requirements.txt --break-system-packages
    
    # ====================================================================
    # STAGE 2: Create the minimal production image
    # ====================================================================
    FROM python:3.13-alpine
    #FROM alpine
    
    # Set the working directory inside the container
    WORKDIR /app
    
    # Add a dedicated non-root user for running the application
    RUN adduser --disabled-password --no-create-home appuser
    
    # Copy the virtual environment from the builder stage
    COPY --from=builder --chown=appuser:appuser /venv /venv
    # Copy the application source code from the host
    COPY main.py .
    # Copy dumb-init executable
    COPY --from=builder --chown=appuser:appuser /usr/bin/dumb-init /usr/bin/dumb-init
    
    # Use the non-root user for security
    USER appuser
    
    # Expose the application port
    EXPOSE 8000
    
    # Use dumb-init as the entrypoint for proper signal handling
    ENTRYPOINT ["/usr/bin/dumb-init", "--"]
    
    # Set the command to run the Uvicorn server via the virtual environment
    CMD ["/venv/bin/uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
    Dockerfile

    Lets build our first docker development image

    docker build -t moreskylab/todo-api:v1.0.0 -f Dockerfile.dev .
    Bash

    now add latest tag

    docker tag moreskylab/todo-api:v1.0.0 moreskylab/todo-api:latest
    Bash

    Push image to docker hub

    docker push moreskylab/todo-api:v1.0.0
    docker push moreskylab/todo-api:latest
    Bash

    OR

    docker push --all-tags moreskylab/todo-api
    Bash

    Discover more from Altgr Blog

    Subscribe to get the latest posts sent to your email.

    Leave a Reply

    Your email address will not be published. Required fields are marked *