Ravindra BagaleCourses & study guides मराठी

Chapter 66: Docker and Kubernetes Together: Dockerfile to Registry to Cluster, Jenkins and Kompose

66.2 Tag and push: local registry, Docker Hub and Amazon ECR

Why: the problem

An image on your laptop is invisible to a cluster. Kubernetes nodes pull images from a registry, so the next step is to give the image a full name and push it somewhere every node can reach. The full name has four parts: registry host, repository path, tag and, after pushing, a digest. 111122223333.dkr.ecr.ap-south-1.amazonaws.com/orders:1.0.0-a1b2c3d says: Amazon ECR in Mumbai, repository orders, tag 1.0.0-a1b2c3d. Tags are labels people choose, and they can move. latest is the worst tag in production: if Nykaa deploys orders:latest today and someone pushes a new latest tomorrow, a node that restarts a pod pulls different code than its neighbours, and nobody can tell which version is running. The fix is an immutable tag per build, usually the version plus the short Git commit, and ECR's tag immutability setting so that tag can never be overwritten. A digest, sha256 of the image manifest, is even stronger: it identifies the exact bytes, and Kubernetes can deploy image@sha256:... so nothing can change underneath you, as we saw in 64.2. Which registry? For learning, a local registry container wired to kind is fastest, because pushes stay on your laptop. kind load docker-image is another shortcut that copies an image straight into the nodes with no registry at all. Docker Hub is the public default and has pull rate limits for anonymous users. On AWS, Amazon ECR keeps images close to EKS nodes, scans them for vulnerabilities, and lets nodes pull through their IAM role without any password. Google Artifact Registry, Azure Container Registry, GitLab and Harbor work the same way. In this lesson we create the kind cluster with a local registry, tag with version and commit, push and read the digest, try kind load, then create an ECR repository with immutable tags and scanning, log in with a short-lived token and push.

What: every term in plain English

  • Registry — Server that stores images, such as Docker Hub, ECR or a local registry:2 container.
  • Repository — A named collection of image versions inside a registry, such as orders.
  • Tag — Human label for an image version; can be moved unless the registry makes tags immutable.
  • Digest — sha256 of the image manifest; identifies exact content and never changes.
  • Fully qualified name — registry/repository:tag, for example localhost:5001/orders:1.0.0-a1b2c3d.
  • kind load docker-image — Copies an image from your Docker into every kind node, skipping registries.
  • Tag immutability — ECR setting that rejects pushing a tag that already exists.
  • Scan on push — ECR scans each pushed image for known vulnerabilities.
  • get-login-password — AWS CLI command that returns a 12-hour ECR token for docker login.
  • Lifecycle policy — ECR rule that deletes old images, such as keep the last 30.
Create a local registry and a kind cluster that trusts it. Tag the image with version and commit. Push to the local registry and read the digest. Try kind load as a shortcut. Create an ECR repository with immutability and scanning. Log in to ECR with a short-lived token. Push to ECR and read scan results.
Tag, push and pull: laptop to registry to nodes

How: step by step

  1. Create a local registry and a kind cluster that trusts it. ch66 ready.
  2. Tag the image with version and commit. Immutable-style tag.
  3. Push to the local registry and read the digest. sha256 shown.
  4. Try kind load as a shortcut. Image on every node.
  5. Create an ECR repository with immutability and scanning. Repo ready.
  6. Log in to ECR with a short-lived token. Login Succeeded.
  7. Push to ECR and read scan results. Digest and findings.
  8. Add a lifecycle policy. Old images cleaned.

A simple rule keeps registries sane across teams: one repository per service, one immutable tag per commit, and promotion by copying the same digest rather than rebuilding. When Nykaa promotes catalog from staging to production, CI does not run docker build again; it pulls the tested digest and pushes it to the production repository or simply reuses it, so what was tested is byte for byte what goes live. Rebuilding for production silently picks up new base image layers and new pip packages, which is how 'it worked in staging' bugs are born. Pair this with a short lifecycle policy, scanning on push, and a pull-through cache for public base images, and the registry stops being a source of surprises during incidents. Write the digest into the release notes and the deployment annotation so anyone on call can match a running pod to its exact build in seconds.

Example 1: Local registry wired to kind

Goal. Local registry wired to kind.

kind-ch66.sh

docker run -d --restart=always -p 127.0.0.1:5001:5000 --name kind-registry registry:2
printf '%s\n' \
 'kind: Cluster' \
 'apiVersion: kind.x-k8s.io/v1alpha4' \
 'name: ch66' \
 'containerdConfigPatches:' \
 '  - |-' \
 '    [plugins."io.containerd.grpc.v1.cri".registry]' \
 '      config_path = "/etc/containerd/certs.d"' \
 'nodes:' \
 '  - role: control-plane' \
 '  - role: worker' \
 '  - role: worker' > kind-ch66.yaml
kind create cluster --config kind-ch66.yaml
for n in $(kind get nodes --name ch66); do
  docker exec $n mkdir -p /etc/containerd/certs.d/localhost:5001
  printf '[host."http://kind-registry:5000"]\n' | docker exec -i $n cp /dev/stdin /etc/containerd/certs.d/localhost:5001/hosts.toml
done
docker network connect kind kind-registry

What each line does

  • registry:2 — The official open-source registry, bound to 127.0.0.1 only.
  • config_path — Tells containerd in each node to read per-registry settings from certs.d.
  • hosts.toml — Maps localhost:5001 inside the node to the registry container on the kind network.
  • printf into files — No heredocs; each line is one argument.
  • docker network connect — Puts the registry on the same Docker network as the nodes.

Green output

$ bash kind-ch66.sh
Creating cluster "ch66" ...
 ✓ Ensuring node image (kindest/node:v1.34.0)
Set kubectl context to "kind-ch66"
$ kubectl get nodes
NAME                 STATUS   ROLES           VERSION
ch66-control-plane   Ready    control-plane   v1.34.0
ch66-worker          Ready    none            v1.34.0
ch66-worker2         Ready    none            v1.34.0

Expected result. Your laptop pushes to localhost:5001 and every node pulls the same name from the registry container, so manifests use one image name everywhere.

Example 2: Tag with version and commit, push, read the digest, try kind load

Goal. Tag with version and commit, push, read the digest, try kind load.

push-local.sh

cd orders-app
git init -q 2>/dev/null; git add -A && git commit -qm "orders 1.0.0" 2>/dev/null
SHA=$(git rev-parse --short HEAD)
TAG=1.0.0-$SHA
docker build -t localhost:5001/orders:$TAG .
docker push localhost:5001/orders:$TAG
docker inspect --format '{{index .RepoDigests 0}}' localhost:5001/orders:$TAG
curl -s http://localhost:5001/v2/orders/tags/list
kind load docker-image orders:dev --name ch66
docker exec ch66-worker crictl images | grep orders

What each line does

  • 1.0.0-SHA — Semantic version for people plus the commit for traceability.
  • docker push — Uploads only layers the registry does not have yet.
  • RepoDigests — The sha256 digest the registry assigned to this exact manifest.
  • /v2/.../tags/list — Registry HTTP API listing the tags of a repository.
  • kind load — Copies orders:dev into all three nodes; good for quick tests, but pods must use imagePullPolicy IfNotPresent.

Green output

$ bash push-local.sh
The push refers to repository [localhost:5001/orders]
5f70bf18a086: Pushed
1.0.0-a1b2c3d: digest: sha256:7d3c9a1b2e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b size: 1782
localhost:5001/orders@sha256:7d3c9a1b2e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b
{"name":"orders","tags":["1.0.0-a1b2c3d"]}
Image: "orders:dev" with ID "sha256:4c1d9e2f7a10..." not yet present on node "ch66-worker", loading...
docker.io/library/orders   dev   4c1d9e2f7a10   171MB

Expected result. The image now has a traceable tag and a fixed digest in a registry the cluster can reach, and kind load showed the no-registry shortcut. In 66.3 we deploy by that tag.

Example 3: Docker Hub: log in with a token, tag and push

Goal. Docker Hub: log in with a token, tag and push.

dockerhub.sh

DOCKERHUB_USER=ravi-demo
export DOCKERHUB_TOKEN=REPLACE_WITH_ACCESS_TOKEN
printf '%s' "$DOCKERHUB_TOKEN" | docker login --username "$DOCKERHUB_USER" --password-stdin
docker tag localhost:5001/orders:1.0.0-a1b2c3d docker.io/$DOCKERHUB_USER/orders:1.0.0-a1b2c3d
docker push docker.io/$DOCKERHUB_USER/orders:1.0.0-a1b2c3d
docker buildx imagetools inspect docker.io/$DOCKERHUB_USER/orders:1.0.0-a1b2c3d | head -3
docker logout

What each line does

  • DOCKERHUB_TOKEN — A personal access token from Docker Hub account settings, never your password; the value here is a placeholder.
  • --password-stdin — Keeps the token out of shell history and the process list.
  • docker tag — Adds a second name, docker.io/USER/orders, pointing at the same image ID.
  • imagetools inspect — Confirms the pushed tag and digest on Docker Hub.
  • docker logout — Removes the stored credential from ~/.docker/config.json on shared machines.

Green output

$ bash dockerhub.sh
Login Succeeded
The push refers to repository [docker.io/ravi-demo/orders]
1.0.0-a1b2c3d: digest: sha256:7d3c9a1b2e4f... size: 1782
Name:      docker.io/ravi-demo/orders:1.0.0-a1b2c3d
MediaType: application/vnd.oci.image.index.v1+json
Digest:    sha256:7d3c9a1b2e4f...
Removing login credentials for https://index.docker.io/v1/

Expected result. Same image, same digest, now public on Docker Hub. A Kubernetes node can pull it with no secret if the repository is public; for a private repository create an imagePullSecret from the same token.

Example 4: Amazon ECR: immutable tags, scanning, login and push

Goal. Amazon ECR: immutable tags, scanning, login and push.

ecr.sh

REGION=ap-south-1
ACCOUNT=111122223333
REPO=$ACCOUNT.dkr.ecr.$REGION.amazonaws.com/orders
aws ecr create-repository --repository-name orders --region $REGION \
  --image-tag-mutability IMMUTABLE \
  --image-scanning-configuration scanOnPush=true \
  --encryption-configuration encryptionType=KMS
aws ecr get-login-password --region $REGION | docker login --username AWS --password-stdin $ACCOUNT.dkr.ecr.$REGION.amazonaws.com
docker tag localhost:5001/orders:1.0.0-a1b2c3d $REPO:1.0.0-a1b2c3d
docker push $REPO:1.0.0-a1b2c3d
docker push $REPO:1.0.0-a1b2c3d
aws ecr describe-image-scan-findings --repository-name orders --image-id imageTag=1.0.0-a1b2c3d --region $REGION --query "imageScanFindings.findingSeverityCounts"

lifecycle.json

{
  "rules": [
    {
      "rulePriority": 1,
      "description": "keep last 30 images",
      "selection": {"tagStatus": "any", "countType": "imageCountMoreThan", "countNumber": 30},
      "action": {"type": "expire"}
    }
  ]
}

What each line does

  • IMMUTABLE — A second push of the same tag is rejected, so 1.0.0-a1b2c3d always means the same image.
  • scanOnPush — Each new image is checked for known CVEs.
  • --password-stdin — The 12-hour token never appears in shell history or process lists.
  • Second push — Shows the immutability rule in action.
  • lifecycle.json — Keeps storage cost flat by expiring older images.

Green output

$ bash ecr.sh
"repositoryUri": "111122223333.dkr.ecr.ap-south-1.amazonaws.com/orders"
Login Succeeded
1.0.0-a1b2c3d: digest: sha256:7d3c9a1b2e4f... size: 1782
tag invalid: The image tag '1.0.0-a1b2c3d' already exists in the 'orders' repository and cannot be overwritten because the repository is immutable.
{
    "LOW": 6,
    "MEDIUM": 2
}
$ aws ecr put-lifecycle-policy --repository-name orders --lifecycle-policy-text file://lifecycle.json --region ap-south-1
"lifecycle": "keep last 30 images"

Expected result. ECR accepted the first push, refused to overwrite the tag, reported no critical findings and will keep only the newest 30 images. On EKS, nodes pull from here through their IAM role with no password in the cluster.

Every command, step by step (kind lab with kubectl)

  1. Registry

    docker run -d -p 127.0.0.1:5001:5000 --name kind-registry registry:2
    

    What it does: Local.

    Status: Downloaded newer image for registry:2
    
  2. Cluster

    kind create cluster --config kind-ch66.yaml
    

    What it does: Three nodes.

    Set kubectl context to "kind-ch66"
    
  3. Network

    docker network connect kind kind-registry
    

    What it does: Reachable.

    (no output)
    
  4. Tag

    docker build -t localhost:5001/orders:1.0.0-$SHA .
    

    What it does: Versioned.

    naming to localhost:5001/orders:1.0.0-a1b2c3d
    
  5. Push

    docker push localhost:5001/orders:1.0.0-a1b2c3d
    

    What it does: Uploaded.

    digest: sha256:7d3c9a1b...
    
  6. Tags

    curl -s http://localhost:5001/v2/orders/tags/list
    

    What it does: Listed.

    {"name":"orders","tags":["1.0.0-a1b2c3d"]}
    
  7. kind load

    kind load docker-image orders:dev --name ch66
    

    What it does: Copied.

    loading...
    
  8. ECR repo

    aws ecr create-repository --repository-name orders --image-tag-mutability IMMUTABLE ...
    

    What it does: Created.

    "repositoryName": "orders"
    
  9. Login

    aws ecr get-login-password | docker login --username AWS --password-stdin ...
    

    What it does: Token.

    Login Succeeded
    
  10. Scan

    aws ecr describe-image-scan-findings --repository-name orders --image-id imageTag=1.0.0-a1b2c3d
    

    What it does: Findings.

    "MEDIUM": 2
    

When it is red 1

http: server gave HTTP response to HTTPS client

Cause. containerd treats the registry as HTTPS.

Fix, step by step

  1. Add hosts.toml with an http host for localhost:5001.

When it is red 2

denied: requested access to the resource is denied

Cause. Not logged in, wrong account in the name, or token expired after 12 hours.

Fix, step by step

  1. Run get-login-password again.
  2. Check the account ID and region in the name.

When it is red 3

toomanyrequests: You have reached your pull rate limit

Cause. Anonymous Docker Hub pulls.

Fix, step by step

  1. Log in, use a pull-through cache in ECR, or mirror base images.

When it is red 4

tag invalid ... cannot be overwritten because the repository is immutable

Cause. Pushing an existing tag.

Fix, step by step

  1. Expected; build a new tag from a new commit.

When it is red 5

ErrImageNeverPull or pull from docker.io after kind load

Cause. Pod uses imagePullPolicy Always or latest tag.

Fix, step by step

  1. Use a fixed tag and imagePullPolicy IfNotPresent.

Practice tasks

Easy Task 1: Registry

Why: Local.

What to do: Start registry:2 on 127.0.0.1:5001.

How:
  1. --restart=always.
Solution
docker ps --filter name=kind-registry

Easy Task 2: Cluster

Why: kind.

What to do: Create ch66 with hosts.toml.

How:
  1. printf.
Solution
kubectl get nodes

Easy Task 3: Push

Why: Local.

What to do: Tag with commit and push.

How:
  1. git rev-parse.
Solution
curl -s http://localhost:5001/v2/orders/tags/list

Medium Task 4: Digest

Why: Read.

What to do: Print RepoDigests.

How:
  1. docker inspect.
Solution
docker inspect --format '{{index .RepoDigests 0}}' localhost:5001/orders:1.0.0-a1b2c3d

Medium Task 5: kind load

Why: Shortcut.

What to do: Load orders:dev into nodes.

How:
  1. crictl images.
Solution
docker exec ch66-worker crictl images

Medium Task 6: ECR

Why: Create.

What to do: Immutable repo with scanning.

How:
  1. KMS encryption.
Solution
aws ecr describe-repositories --repository-names orders

Hard Task 7: Lifecycle

Why: Clean.

What to do: Add lifecycle.json.

How:
  1. Keep 30.
Solution
aws ecr get-lifecycle-policy --repository-name orders

Hard Task 8: Nykaa

Why: Main task.

What to do: Local and ECR flow for catalog.

How:
  1. Second push rejected.
Solution
aws ecr list-images --repository-name catalog

Homework: try at home

For Nykaa's catalog image, run a local registry for kind, push catalog:2.3.0-COMMIT and read its digest, then create an ECR repository in ap-south-1 with immutable tags, scan on push and a keep-20 lifecycle policy, push the same image and prove the second push is rejected.

Recap

  • Nodes pull from registries, so every image needs a full registry/repository:tag name.
  • Tag each build with version plus commit; never deploy latest.
  • Digests identify exact content and can be used in manifests.
  • A local registry or kind load makes kind labs fast.
  • ECR adds immutable tags, scanning, lifecycle rules and password-free pulls for EKS nodes.

FAQ

Q. Should I deploy by tag or by digest?

Immutable tags are readable and safe enough with ECR immutability. Digests are strictest; many teams store both, tag for people and digest in the manifest written by CI.

Q. How do EKS nodes pull from ECR without a password?

The node IAM role has the AmazonEC2ContainerRegistryReadOnly policy, and the kubelet credential provider fetches tokens automatically.

Q. What is a pull-through cache?

An ECR feature that caches images from Docker Hub, Quay or other public registries in your account, avoiding rate limits and outages.

Q. Can I sign images?

Yes. AWS Signer with Notation or Sigstore cosign can sign images, and admission policies can block unsigned ones.

Interview questions

  1. Explain image name, tag and digest.

    Model answer: Name is registry and repository, tag is a movable label, digest is the content hash that never changes.

  2. Why is latest dangerous in Kubernetes?

    Model answer: Different nodes can pull different images under the same name, and rollbacks become impossible to reason about.

  3. How do you secure a container registry?

    Model answer: Private repositories, IAM or tokens, immutable tags, scanning, signing and lifecycle policies.

  4. How do you speed up image pulls?

    Model answer: Small images, shared base layers, registry close to nodes, and pre-pulling or caching.

Quick quiz

Q1. Why avoid latest?

It moves; nodes may run different code.

Q2. What identifies exact image bytes?

The digest.

Q3. How long is an ECR login token valid?

12 hours.

Q4. What blocks overwriting a tag in ECR?

Tag immutability.

Q5. What does kind load do?

Copies an image into kind nodes without a registry.

Samajla ka?