Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

Container Registry Guide: Docker Hub vs GitLab vs ECR

By Kokil Thapa | Last reviewed: September 2026

Your build pipeline produces a Docker image. Now you need a place to store it, scan it, and pull it on every deploy. This Container Registry Guide: Docker Hub vs GitLab vs ECR walks through the three registries most teams actually evaluate in 2026. I've used all three on production Laravel and GitLab CI pipelines that ship to VPS and AWS. The choice is rarely about raw storage. It is about where your code lives, where your servers run, and who pays the egress bill.

What is a container registry and why does your deploy pipeline need one?

A container registry stores versioned OCI/Docker images. Your CI job builds an image, tags it, and pushes it. Production servers or orchestrators pull that exact tag before starting containers. Without a registry, every server would need to rebuild from source on every deploy. That is slow, inconsistent, and hard to roll back.

Think of the registry as the artifact store between docker build and docker run. On real client projects I maintain with Deployer and GitLab CI, the registry is the handoff point. Build once in CI. Pull the same digest everywhere. If you are new to the runtime side, read the Docker install guide for Ubuntu first, then wire the registry into your pipeline.

Container Registry Push and Pull FlowGit RepoSource codeCI Builddocker buildRegistryTagged imagesProductiondocker pullImage identity: repository:tag @ sha256:digestTags move. Digests are immutable.Pin deploys to digest for reproducible rollbacks.Scan at push. Pull only signed or approved images.
Container Registry Guide workflow: CI builds once, stores the image, and production pulls by tag or digest

Every registry in this guide speaks the same Docker Registry HTTP API V2. That means docker login, docker push, and docker pull work the same way. Differences show up in authentication, pricing, retention rules, and how tightly the registry connects to your CI/CD platform.

Core concepts you will see in every registry

  • Repository: A named image namespace, such as myapp/api.
  • Tag: A movable pointer like v1.4.2 or main-abc123.
  • Digest: The immutable SHA256 hash of the image manifest.
  • Retention policy: Rules that delete old tags to control storage cost.
  • Pull rate limits: Throttling on anonymous or free-tier pulls.

For tagging conventions that survive production, see the dedicated Docker image tagging strategies article. Good tags reduce confusion when three registries each show a different UI.

How does Docker Hub compare to GitLab Container Registry and AWS ECR?

Docker Hub is the default public registry most developers meet first. GitLab Container Registry ships inside GitLab and pairs naturally with GitLab CI. AWS ECR is Amazon's managed registry built for ECS, EKS, and Lambda container workflows. All three store the same image format. They diverge on auth models, free tiers, and operational fit.

The table below is the heart of this Container Registry Guide: Docker Hub vs GitLab vs ECR. Numbers shift with AWS list pricing and your GitLab tier, but the trade-offs hold for small teams in Nepal and larger global deployments alike.

CriteriaDocker HubGitLab Container RegistryAWS ECR
Best fitPublic base images, OSS, quick sharingGitLab-hosted repos + CI on same platformAWS-native deploys (ECS, Fargate, EKS)
Private repos (free tier)1 private repo (rate-limited pulls)Included with GitLab project visibilityPay per GB stored + data transfer
AuthenticationUsername + access tokenCI_JOB_TOKEN, deploy token, or PATIAM roles, aws ecr get-login-password
CI/CD integrationGeneric; works everywhereNative in GitLab CI (docker push in job)Native in CodePipeline, GitLab, GitHub Actions
Image scanningDocker Scout (paid tiers)Dependency + container scanning (Ultimate tier)Basic scan on push; enhanced with Inspector
Geo / latencyGlobal CDN; good for public pullsFollows your GitLab instance regionRegional; best inside same AWS region
Typical cost painPull rate limits, paid private reposStorage growth on self-managed runnersEgress outside AWS, cross-region replication

Docker Hub remains the largest public catalog. Official images like php:8.3-fpm and mysql:8.4 start here. That is why even ECR-only shops still pull base layers from Docker Hub unless they mirror them internally.

GitLab's registry lives at registry.gitlab.com/<group>/<project>. On sister sites I deploy with Deployer 7 and GitLab CI, the registry sits beside the repo. No extra vendor account. The job token authenticates the push. That simplicity matters when your team is two developers and one part-time DevOps person.

ECR repositories live in a specific AWS region, for example 123456789012.dkr.ecr.ap-south-1.amazonaws.com/myapp. Mumbai (ap-south-1) is the usual pick for Nepal-facing AWS workloads. Pulls from EC2 or Fargate in the same region avoid public-internet egress charges. For a full ECS path, see deploy containers on Amazon ECS with Fargate.

Which Registry Fits Your Stack?Where does CI run?GitLab CIUse GitLab RegistryOther CICheck deploy targetAWS ECS / EKSPrefer AWS ECRVPS / bare metalDocker Hub or GitLabPublic OSS images: Docker Hub for discovery
Container Registry Guide decision tree: match registry choice to CI platform and deployment target

How do you push and pull images to Docker Hub, GitLab, and ECR in CI/CD?

All three registries follow the same CLI pattern. Log in, tag the local image with the remote name, push. Production pulls with the same remote name. The login step is where each platform differs.

Docker Hub: login, tag, and push

Create a Docker Hub access token instead of using your account password. Store it in CI variables. Official docs live at Docker Hub access tokens.

# Local or CI
echo "$DOCKERHUB_TOKEN" | docker login -u "$DOCKERHUB_USER" --password-stdin

docker build -t myapp:1.0.0 .
docker tag myapp:1.0.0 docker.io/myorg/myapp:1.0.0
docker push myorg/myapp:1.0.0

Watch pull rate limits on the free tier. Anonymous pulls cap at 100 per six hours per IP. Authenticated free accounts get higher limits, but busy deploy fleets still hit walls. Mirror critical base images to your private registry if production depends on them.

GitLab Container Registry in a GitLab CI job

GitLab exposes predefined variables for registry address and credentials. A typical Laravel pipeline builds a PHP-FPM image and pushes on every merge to main. Full walkthrough: deploy a Laravel app with GitLab CI/CD to a VPS.

build-image:
  stage: build
  image: docker:27-cli
  services:
    - docker:27-dind
  variables:
    DOCKER_TLS_CERTDIR: "/certs"
    IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
  script:
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
    - docker build -t $IMAGE_TAG .
    - docker push $IMAGE_TAG

CI_JOB_TOKEN scopes access to the current project by default. For cross-project pulls, use a deploy token or group-level token. The GitLab CI YAML deep dive for PHP projects covers variable scoping and cache keys in more detail.

On projects like Translation Nepal and other sister sites sharing one GitLab CI pattern, the registry URL matches the GitLab hostname. Self-managed GitLab uses your domain instead of registry.gitlab.com.

AWS ECR: create repo and push from CI

ECR auth tokens expire after 12 hours. CI jobs fetch a fresh password on each run. Official reference: Amazon ECR registry authentication.

aws ecr get-login-password --region ap-south-1 \
  | docker login --username AWS --password-stdin \
    123456789012.dkr.ecr.ap-south-1.amazonaws.com

aws ecr create-repository --repository-name myapp/api --region ap-south-1

docker build -t myapp/api:1.0.0 .
docker tag myapp/api:1.0.0 \
  123456789012.dkr.ecr.ap-south-1.amazonaws.com/myapp/api:1.0.0
docker push 123456789012.dkr.ecr.ap-south-1.amazonaws.com/myapp/api:1.0.0

Attach an IAM policy that allows ecr:GetAuthorizationToken, ecr:BatchCheckLayerAvailability, ecr:PutImage, and ecr:InitiateLayerUpload for CI push roles. Production pull roles need only read actions. Least privilege here prevents a compromised CI key from deleting your entire catalog.

GitLab CI to Registry to VPS DeployGit PushTriggers CIBuild Jobdocker buildGitLab RegistryPush tagged imageDeploy JobSSH + pullProduction VPS runs docker compose pull && up -dSame digest as CI build — no rebuild on serverRollback = redeploy previous tag from registryPattern used on shared EC2 Laravel deployments
Typical GitLab CI pipeline: build and push to Container Registry, then pull on production VPS

Local development still matters

Registries are not just for production. A Docker Compose multi-container local setup can pull private images if you log in once on your laptop. Keep local tags separate from production tags to avoid accidental pushes of debug layers.

Which container registry should you choose for Laravel and PHP deployments?

Laravel apps containerize cleanly with PHP-FPM, Nginx, and a queue worker as separate services or a single fat image. The registry choice follows your hosting bill, not your framework.

GitLab Registry when CI and code already live in GitLab

This is my default for Laravel 12 and Laravel 13 projects on GitLab CI plus VPS deploy. PHP 8.3 or 8.4 base images pull from Docker Hub. Your application image pushes to GitLab Registry. Deployer or SSH scripts pull on the server. No AWS account required. Storage cost stays inside your GitLab plan.

For enterprise clients needing audit trails and environment promotion, pair the registry with protected branches and manual deploy gates. See enterprise application development services for how that maps to longer-lived platforms like booking systems on Adventure Third Pole Trek.

ECR when the app runs on AWS

If you moved from a VPS to ECS Fargate, ECR is the path of least resistance. Task definitions reference ECR URIs. IAM task roles pull without embedding long-lived passwords. Cross-account ECR works for agency setups where CI lives in one AWS account and production in another.

ECR pricing is pay-as-you-go. Storage runs roughly USD 0.10 per GB-month in most regions. Data transfer out to the public internet adds cost. Budget Rs 3,000–8,000/month (~USD 22–60) for small Laravel fleets before optimization. Use the JSON formatter tool to inspect ECR lifecycle policy JSON before you apply it.

Docker Hub for public OSS and base images

Ship a open-source Laravel package or CLI tool? Docker Hub gives you discovery and zero-config pulls for users worldwide. Keep private client images off the free single-private-repo tier. It runs out fast on active projects.

Docker Hub Pro adds unlimited private repos and higher pull limits for about USD 5–9 per user/month. That can beat operational time spent mirroring images yourself if your team is tiny.

Hybrid pattern I see in production

  1. Pull vetted base images from Docker Hub in CI.
  2. Push application images to GitLab Registry or ECR.
  3. Scan with Trivy before push (see container image scanning with Trivy).
  4. Pin production deploys to digest, not a floating latest tag.
  5. Mirror critical bases to your private registry if Docker Hub limits hurt deploys.

That hybrid keeps base-image maintenance on the community while your application layers stay private and auditable. It also aligns with Linux system administration work where the same person manages CI, the registry ACLs, and the production host.

How do you secure container images across Docker Hub, GitLab, and ECR?

A registry without scanning and access control is just a tarball host. Security belongs at push time, at pull time, and in retention policy.

Scan on every push

GitLab Ultimate includes container scanning in the pipeline. ECR runs basic scanning automatically in many regions. Docker Hub offers Docker Scout on paid plans. For a vendor-neutral option, run Trivy in CI regardless of registry:

trivy image --severity HIGH,CRITICAL --exit-code 1 myapp:$CI_COMMIT_SHORT_SHA

Fail the pipeline on critical CVEs you cannot accept. Document exceptions in your security policy. Deeper guide: Trivy scan containers and IaC for vulnerabilities.

Sign images and enforce trust

Signing with Cosign lets production nodes accept only signed images. That closes the gap if someone gains push access to your registry. Walkthrough: sign container images with Cosign.

Retention and cleanup

Untagged manifests and stale preview tags eat storage. ECR lifecycle policies delete images older than N days. GitLab cleanup policies run on schedule. Docker Hub needs manual or API-driven pruning on paid tiers. Without cleanup, a busy CI pipeline can store hundreds of gigabytes within a year.

Secure Container Registry PipelineBuilddocker buildScanTrivy / ScoutSignCosign keyPush RegistryECR / GitLabPolicy gate: block CRITICAL CVEsUnsigned images rejected at deployProduction pull: IAM role or deploy tokenVerify digest matches signed manifestApplies to Docker Hub, GitLab, and ECR
Secure Container Registry Guide pipeline: scan and sign before push, enforce policy at deploy

Secrets and credentials hygiene

Never bake .env secrets into image layers. Inject them at runtime from the orchestrator or host. Rotate registry tokens quarterly. Use read-only tokens on production servers. For API-heavy Laravel apps that share credentials across services, see API development practices and keep registry credentials out of application repos entirely.

GitLab documents registry permissions at GitLab Container Registry. Match project visibility to image sensitivity. A public GitLab project exposes its registry unless you lock it down explicitly.

What are common container registry mistakes in production?

Teams often pick a registry because someone already has an account. That works until rate limits, egress bills, or auth expiry break a Friday deploy.

  • Floating latest tags in production: You cannot roll back what you cannot identify. Pin digests.
  • Ignoring pull limits on Docker Hub: Autoscaling groups hammer anonymous limits fast.
  • ECR tokens in cron instead of IAM roles: Expired tokens cause silent pull failures.
  • No lifecycle policy: Storage cost climbs while nobody deletes old preview tags.
  • Scanning only on deploy: Scan at push so bad images never enter the catalog.
  • Single registry for everything: Mirror critical public bases locally for resilience.

After incidents I handle under support and maintenance, the root cause is often an auth token that expired months ago. Monitor pull errors in deploy logs the same way you monitor HTTP 500 responses.

Compare this guide with the broader GitHub Actions vs GitLab CI comparison if your registry choice depends on switching CI platforms. The registry should follow CI and cloud placement, not the other way around.

Key Takeaways

  • Match registry to CI and deploy target: GitLab Registry for GitLab CI + VPS, ECR for AWS, Docker Hub for public images.
  • Authenticate with tokens or IAM roles — never commit registry passwords to git.
  • Tag with commit SHA or semver; pin production deploys to immutable digests.
  • Scan with Trivy on every push and block critical CVEs before images enter the catalog.
  • Set retention policies early so CI tag sprawl does not inflate storage bills.
  • Keep base images on Docker Hub or a mirror; store private application layers in GitLab or ECR.

People Also Ask

Is GitLab Container Registry free?

GitLab includes a private Container Registry with every project on Free, Premium, and Ultimate tiers. Storage limits depend on your GitLab.com plan or self-managed disk. You pay in runner minutes and disk, not per-repository fees like Docker Hub's old private-repo caps.

Can I use Docker Hub and ECR together?

Yes. Most teams pull public base images from Docker Hub during CI, then push the final application image to ECR or GitLab Registry. ECR also supports pull-through cache rules to mirror Docker Hub images into your AWS account for faster, rate-limit-free pulls.

How is AWS ECR different from Docker Hub?

ECR is regional, private by default, and authenticated through IAM. Docker Hub is global, strong for public discovery, and authenticated with user tokens. ECR integrates natively with ECS and EKS. Docker Hub integrates with every CI platform but imposes pull rate limits on free usage.

Which registry is best for a small team in Nepal on a VPS?

GitLab Container Registry plus GitLab CI is usually the lowest-friction choice if your code is already on GitLab. Total cost stays predictable on a single VPS around Rs 2,000–5,000/month (~USD 15–37) for hosting. Docker Hub works for public tools. ECR only pays off once you migrate workloads to AWS.

Pick the registry your deploy path already implies

This Container Registry Guide: Docker Hub vs GitLab vs ECR boils down to one question: where does your image need to land after CI builds it? GitLab teams should push to GitLab Registry. AWS teams should use ECR. Docker Hub stays the public catalog and base-image source for everyone else. Add scanning, signing, and lifecycle rules on day one — not after your first compromised layer or storage surprise.

If you want help wiring registry auth into GitLab CI, ECS, or a Laravel VPS deploy pipeline, contact us or browse the portfolio for containerized projects already in production. Read more DevOps articles on the blog, validate pipeline JSON with the regex tester, and see what clients say in customer reviews.

Frequently Asked Questions

A container registry stores versioned OCI/Docker images. Your CI job builds an image, tags it, and pushes it. Production servers or orchestrators pull that exact tag or digest before starting containers. Without a registry, every server would rebuild from source on every deploy, which is slow, inconsistent, and hard to roll back. Think of it as the artifact store between docker build and docker run.

All three store the same image format and speak the Docker Registry HTTP API V2, so docker login, push, and pull work the same way. They diverge on auth, pricing, and operational fit. Docker Hub suits public base images and quick sharing with a global CDN. GitLab Container Registry pairs natively with GitLab CI when code and pipelines already live on GitLab. AWS ECR fits AWS-native deploys to ECS, Fargate, or EKS with IAM roles and low-latency pulls inside the same VPC and region.

Match the registry to where your CI and servers already run, not to Laravel itself. For Laravel 12 or 13 on GitLab CI deploying to a VPS, GitLab Container Registry is the practical default: PHP 8.3 or 8.4 base images pull from Docker Hub, your application image pushes beside the repo, and Deployer or SSH scripts pull on the server. If the app runs on ECS Fargate or EKS, ECR is the path of least resistance because task definitions reference ECR URIs and IAM roles pull without long-lived passwords.

ECR storage runs roughly USD 0.10 per GB-month in most regions. Budget Rs 3,000–8,000/month (~USD 22–60) for small Laravel fleets before optimization.

Anonymous pulls cap at 100 per six hours per IP. Authenticated free accounts get higher limits, but busy deploy fleets still hit walls during autoscaling or frequent redeploys. That is why production teams mirror critical base images to a private registry instead of depending on anonymous Docker Hub pulls. Docker Hub Pro raises pull limits and adds unlimited private repos for about USD 5–9 per user/month, which can beat the operational time of running your own mirror if the team is small.

Use Docker Hub for public OSS images, official base layers, and quick worldwide sharing—not as your sole private production store.

GitLab exposes predefined variables such as CI_REGISTRY, CI_REGISTRY_USER, CI_REGISTRY_PASSWORD, and CI_REGISTRY_IMAGE. A typical job logs in with docker login, builds the image, tags it with $CI_REGISTRY_IMAGE and a commit SHA, then pushes. CI_JOB_TOKEN scopes access to the current project by default. For cross-project pulls, use a deploy token or group-level token. On sister sites I deploy with Deployer 7 and GitLab CI, the registry URL sits beside the repo at registry.gitlab.com/group/project with no extra vendor account.

ECR auth tokens expire after 12 hours, so CI jobs fetch a fresh password on each run with aws ecr get-login-password piped into docker login. Attach an IAM policy allowing ecr:GetAuthorizationToken, ecr:BatchCheckLayerAvailability, ecr:PutImage, and ecr:InitiateLayerUpload for CI push roles. Production pull roles need only read actions. On ECS or Fargate in the same region, IAM task roles pull without embedding long-lived passwords. Storing ECR tokens in cron instead of IAM roles is a common mistake that causes silent pull failures when tokens expire.

ECR repositories live in a specific AWS region, for example 123456789012.dkr.ecr.ap-south-1.amazonaws.com/myapp. Mumbai (ap-south-1) is the usual pick for Nepal-facing AWS workloads. Pulls from EC2 or Fargate in the same region avoid public-internet egress charges that inflate bills when images cross regions or leave AWS entirely. If your servers stay on a Kathmandu VPS but CI pushes to ECR in another continent, latency and transfer cost both suffer. Place the registry where production actually runs.

Pull vetted base images such as php:8.3-fpm or mysql:8.4 from Docker Hub during CI, then push your application image to GitLab Container Registry or ECR. Scan with Trivy before push, pin production deploys to digest rather than a floating latest tag, and mirror critical public bases locally if Docker Hub rate limits threaten deploys. This keeps community-maintained base layers accessible while your application layers stay private, auditable, and tied to your CI platform. It also reduces single-vendor dependency when one registry throttles or has an outage.

Security belongs at push time, pull time, and in retention policy. Scan on every push: GitLab Ultimate includes container scanning, ECR runs basic scanning on push in many regions, and Docker Hub offers Docker Scout on paid tiers. Run Trivy in CI regardless of registry and fail the pipeline on critical CVEs. Sign images with Cosign so production accepts only signed images. Never bake .env secrets into image layers; inject them at runtime. Rotate registry tokens quarterly and use read-only tokens on production servers. Match GitLab project visibility to image sensitivity because public projects expose their registry unless locked down.

Teams often pick a registry because someone already has an account until rate limits, egress bills, or auth expiry break a Friday deploy. Floating latest tags prevent rollback because you cannot identify what ran. Ignoring Docker Hub pull limits causes autoscaling groups to fail silently. Storing expired ECR tokens in cron instead of IAM roles breaks pulls months later. Skipping lifecycle policies lets preview tags consume hundreds of gigabytes within a year. Scanning only at deploy instead of push lets bad images enter the catalog. Monitor pull errors in deploy logs the same way you monitor HTTP 500 responses.

GitLab Container Registry is included with GitLab project visibility rather than billed as a separate per-gigabyte service like ECR. Storage growth still matters, especially on self-managed GitLab with local runners, because busy CI pipelines accumulate tagged images quickly. Use GitLab cleanup policies on a schedule to prune stale preview tags and untagged manifests. For teams already paying for GitLab, this bundling removes an extra vendor account and keeps registry ACLs aligned with repository permissions. That simplicity helps when your team is two developers and one part-time DevOps person.

Untagged manifests and stale preview tags eat storage silently. ECR lifecycle policies delete images older than N days and should be reviewed with the JSON formatter tool before applying. GitLab cleanup policies run on schedule to prune old tags. Docker Hub requires manual or API-driven pruning on paid tiers. Without cleanup, a busy CI pipeline storing a new tag per commit can reach hundreds of gigabytes within a year. Set retention rules early rather than reacting after a storage invoice surprises you. Pair policies with digest-pinned deploys so deletions never remove the image currently running in production.

Yes, and many production Laravel pipelines do exactly that. Docker Hub remains the largest public catalog, so official base images often start there even when application images live elsewhere. In CI, pull php:8.3-fpm from Docker Hub, build your Laravel application layer, then push the result to GitLab Registry for VPS deploys or to ECR for ECS Fargate. All three registries speak the same Docker Registry HTTP API V2, so only authentication and remote naming differ between steps. The registry choice should follow CI platform and cloud placement, not force you onto one vendor for every layer.

Share this article

0 Comments

Leave a comment

Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

Quick Contact Options
Choose how you want to connect me: