
September 09, 2026
14 min read
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.
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.2ormain-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.
| Criteria | Docker Hub | GitLab Container Registry | AWS ECR |
|---|---|---|---|
| Best fit | Public base images, OSS, quick sharing | GitLab-hosted repos + CI on same platform | AWS-native deploys (ECS, Fargate, EKS) |
| Private repos (free tier) | 1 private repo (rate-limited pulls) | Included with GitLab project visibility | Pay per GB stored + data transfer |
| Authentication | Username + access token | CI_JOB_TOKEN, deploy token, or PAT | IAM roles, aws ecr get-login-password |
| CI/CD integration | Generic; works everywhere | Native in GitLab CI (docker push in job) | Native in CodePipeline, GitLab, GitHub Actions |
| Image scanning | Docker Scout (paid tiers) | Dependency + container scanning (Ultimate tier) | Basic scan on push; enhanced with Inspector |
| Geo / latency | Global CDN; good for public pulls | Follows your GitLab instance region | Regional; best inside same AWS region |
| Typical cost pain | Pull rate limits, paid private repos | Storage growth on self-managed runners | Egress 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.
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.
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
- Pull vetted base images from Docker Hub in CI.
- Push application images to GitLab Registry or ECR.
- Scan with Trivy before push (see container image scanning with Trivy).
- Pin production deploys to digest, not a floating
latesttag. - 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.
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
latesttags 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
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.

