Push Podman Images to a Container Registry

Tested on Red Hat Enterprise Linux 10.2 (Coughlan)
Package podman-5.8.2-5.el10_2.x86_64
Applies to Any Linux host with Podman installed
Privilege Rootful examples on the lab host; rootless uses the same push syntax with a separate local image store
Scope Publishing local images and manifest lists with podman push and podman manifest push, registry naming for Docker Hub and Quay, private-registry TLS boundaries, --digestfile, retries, brief --authfile usage, transport overview, verification by pull, and podman image scp for direct transfer. Does not cover registry deployment, login internals, multi-architecture image builds, or image signing.
Related guides Pull images with podman pull
Manage images with podman images

You already built or pulled an image into local Podman storage. podman push uploads that image — its layers and manifest — to a remote registry so other hosts, CI jobs, or orchestrators can pull it. This guide walks through the two common push shapes, registry-specific naming, flags that help in production, and podman image scp when a registry is not the right hop.


Push an image to a registry

Start from a local reference you can see in storage:

bash
podman images localhost/myapp:v1

Sample output:

output
REPOSITORY       TAG         IMAGE ID      CREATED      SIZE
localhost/myapp  v1          5dd467fce50b  2 years ago  787 kB

The destination must include the registry hostname. A name like myapp:v1 alone is not enough — Podman needs to know which registry API to contact:

text
registry.example.com/project/myapp:v1

The tag-first workflow creates that remote-shaped name locally, then pushes it:

bash
podman tag localhost/myapp:v1 registry.example.com:5001/project/myapp:v1

podman tag adds a second name for the same image ID; it does not copy layers. Push the tagged reference:

bash
podman push registry.example.com:5001/project/myapp:v1

Sample output:

output
Getting image source signatures
Copying blob sha256:2114fc8b70586b9325dde6fd26066d9951414dcdfb3995f41d51d1995cf3bd9d
Copying config sha256:5dd467fce50b56951185da365b5feee75409968cbab5767b9b59e325fb2ecbc0
Writing manifest to image destination

Each Copying blob line is a layer upload (or a registry-side blob reuse). Writing manifest to image destination means the tag on the registry now points at the manifest you just published.


Push without retagging locally

Podman accepts separate source and destination arguments, so you do not need another local tag just to spell the remote path:

bash
podman push localhost/myapp:v1 registry.example.com:5001/project/myapp:v2-direct

The push transcript matches the tag-first flow — same blobs, same manifest write line. Think of the arguments as:

text
source image      → local storage reference (what you have now)
destination       → target registry reference (where it should land)
Workflow Best when
Tag first, then push You want the remote name to stay in podman images for later runs
Source + destination on one push One-off publication or scripts that should not clutter local tags

The local localhost/myapp:v1 image is unchanged either way; only the registry receives a new tag.


Authenticate before pushing

Registries that require credentials reject anonymous pushes. Log in to the registry host first — see Log in to a container registry for auth.json paths, --password-stdin, and rootless vs rootful stores:

bash
podman login registry.example.com:5001

After Login Succeeded!, retry the push. Podman reads stored credentials for that registry hostname on each layer upload.


Push to Docker Hub

Docker Hub lives at the docker.io registry. User images use your Docker ID as the namespace:

text
docker.io/USERNAME/myapp:v1

Example:

bash
podman push localhost/myapp:v1 docker.io/USERNAME/myapp:v1

Replace USERNAME with your Docker Hub account. The docker.io/library/... path is reserved for official-library-style images on Docker Hub; personal and organization images use docker.io/<namespace>/....

Log in with podman login docker.io (or podman login with no host, which defaults to Docker Hub) before pushing. Rate limits and credential storage behave the same as for pulls — only the direction changes.


Push to Quay

Red Hat Quay uses the quay.io hostname:

bash
podman push localhost/myapp:v1 quay.io/USERNAME/myapp:v1

Log in with podman login quay.io, then push. Organization projects extend the repository path (quay.io/myorg/myapp:v1) the same way as pulls — the registry hostname and full repository path must match what Quay expects for your account.


Push to a private registry

Private registries often include a port and a project path:

bash
podman push localhost/myapp:v1 registry.example.com:5000/project/myapp:v1

When the registry presents a certificate signed by your organization's CA, install that CA into the system or container trust store and keep TLS verification enabled. Configure a private registry covers lab and production registry setup; this page assumes the registry already exists and is reachable.


Use --tls-verify=false only in a controlled lab

Some lab registries use self-signed TLS or plain HTTP. Podman can skip certificate verification for a single push:

bash
podman push --tls-verify=false localhost/myapp:v1 localhost:5000/myapp:v1

That flag is appropriate only when you deliberately accept insecure transport — for example a throwaway registry on loopback during learning. For a real private registry:

  • Install the registry CA where Podman trusts it
  • Configure registries.conf when the registry is insecure HTTP
  • Leave TLS verification on for production pushes

Persistent trust and registries.conf entries belong in the private-registry guide, not repeated here.


Point push at a specific auth file

When credentials live outside the default runtime auth.json, pass the file explicitly:

bash
podman push --authfile ~/.config/containers/private-auth.json localhost/myapp:v1 registry.example.com:5001/project/myapp:v1

The same path works for podman login --authfile and podman pull --authfile. CI jobs often set REGISTRY_AUTH_FILE to the temporary credentials file the pipeline created so every registry command in the job reads one store. Full auth-file layout and helper configuration stay in the login article — here the point is that push honors the same overrides as pull.


Retry failed pushes

Transient network blips or brief registry outages can interrupt a push. Podman can retry automatically:

bash
podman push --retry 5 localhost/myapp:v1 registry.example.com:5001/project/myapp:v1

Add a delay between attempts when the registry asks you to back off:

bash
podman push --retry 5 --retry-delay 10s localhost/myapp:v1 registry.example.com:5001/project/myapp:v1

Retries help with transport and temporary server errors. They do not fix:

  • Missing or wrong login (authentication required, unauthorized)
  • Repository permission denied
  • Unknown CA or hostname TLS failures
  • Registry policy rejection (quota, immutability rules)

Fix credentials, trust, or repository settings first; then retries matter.


Capture the remote digest with --digestfile

Tags move; digests identify exact manifest content. After a successful push, write the remote manifest digest to a file:

bash
podman push --digestfile pushed-digest.txt localhost/myapp:v1 registry.example.com:5001/project/myapp:v3-digest

Read what was recorded:

bash
cat pushed-digest.txt

Sample output:

output
sha256:ccfc769b27e3717ea97b8262cd3e6f88cb2327ebf33e2f97275667a30149264d

That value is the digest of the manifest that landed on the registry — useful for release pipelines, comparing what you published against what a cluster pulled, and building an immutable reference:

text
registry.example.com:5001/project/myapp@sha256:ccfc769b27e3717ea97b8262cd3e6f88cb2327ebf33e2f97275667a30149264d

The tag v3-digest can still be moved to a different build later; only the @sha256:... form pins content.


Verify the pushed image

The straightest check is to remove the local copy of the remote tag and pull it back:

bash
podman rmi registry.example.com:5001/project/myapp:v1

If Podman refuses because a container still references the image, remove or retag that container first. Pull from the registry:

bash
podman pull registry.example.com:5001/project/myapp:v1

Sample output:

output
Trying to pull registry.example.com:5001/project/myapp:v1...
Getting image source signatures
Copying blob sha256:ce980a8f5545faa3125a489aad32c00d6cf13d80a302308c3963b524085657af
Copying config sha256:5dd467fce50b56951185da365b5feee75409968cbab5767b9b59e325fb2ecbc0
Writing manifest to image destination
5dd467fce50b56951185da365b5feee75409968cbab5767b9b59e325fb2ecbc0

The image ID should match the source you pushed (5dd467fce50b in this lab). Skopeo can inspect a remote manifest without a full pull when it is installed; this lab verified with pull only.


Push a multi-architecture manifest list

When you already built a manifest list locally — see Create multi-architecture images for construction — publish it with podman manifest push:

bash
podman manifest inspect localhost/myapp-multi:v1

Sample output (truncated):

output
{
    "schemaVersion": 2,
    "mediaType": "application/vnd.oci.image.index.v1+json",
    "manifests": [
        {
            "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
            "digest": "sha256:43de9874507eaa8ffd88eac885b672b1dfc57cc583d9ad920850f97f19809f8f",
            "platform": {
                "architecture": "amd64",
                "os": "linux"
            }
        }
    ]
}

Push the index and its member images (default --all behavior):

bash
podman manifest push localhost/myapp-multi:v1 docker://registry.example.com:5001/project/myapp-multi:v1

Sample output:

output
Getting image list signatures
Copying 1 images generated from 1 images in list
Copying image sha256:43de9874507eaa8ffd88eac885b672b1dfc57cc583d9ad920850f97f19809f8f (1/1)
Getting image source signatures
Copying blob sha256:2114fc8b70586b9325dde6fd26066d9951414dcdfb3995f41d51d1995cf3bd9d
Copying config sha256:5dd467fce50b56951185da365b5feee75409968cbab5767b9b59e325fb2ecbc0
Writing manifest to image destination
Writing manifest list to image destination
Storing list signatures

podman manifest push is the dedicated command for publishing and controlling manifest lists or OCI indexes. Plain podman push can also push a manifest list or image index, but this guide uses podman manifest push for the explicit multi-architecture workflow.


podman push transports

Registry pushes are the common case, but Podman speaks several destination transports. Prefix or scheme tells the copy engine where blobs should land:

Destination Typical use
docker://REGISTRY/... Container registry (explicit transport; optional when the name is unambiguous)
oci-archive:/path/file.tar OCI layout tar archive on disk
docker-archive:/path/file.tar Legacy Docker save tarball
dir:/path containers/image directory transport

Archive and directory exports are covered in Save, load, export and import images. This article stays on registry publication; the table is here so a docker:// prefix on podman manifest push is not surprising.


Copy an image between hosts with podman image scp

podman image scp transfers images between Podman stores by saving an archive, transferring it when necessary, and loading it at the destination. Remote-host copies typically use SSH; same-host rootful/rootless transfers do not require sshd. Syntax from Podman 5.8.2:

text
podman image scp [options] IMAGE [HOST::]

Copy into rootful storage on the same host (rootless → rootful on localhost):

bash
sudo podman image scp localhost/myapp:v1 root@localhost::localhost/myapp-scp:v1

Sample output:

output
Copying blob sha256:2114fc8b70586b9325dde6fd26066d9951414dcdfb3995f41d51d1995cf3bd9d
Copying config sha256:5dd467fce50b56951185da365b5feee75409968cbab5767b9b59e325fb2ecbc0
Writing manifest to image destination
Getting image source signatures
Copying blob sha256:2114fc8b70586b9325dde6fd26066d9951414dcdfb3995f41d51d1995cf3bd9d
Copying config sha256:5dd467fce50b56951185da365b5feee75409968cbab5767b9b59e325fb2ecbc0
Writing manifest to image destination
Loaded image: localhost/myapp:v1

Confirm the image arrived under the new name in the destination store:

bash
sudo podman images localhost/myapp-scp:v1

Sample output:

output
REPOSITORY           TAG         IMAGE ID      CREATED      SIZE
localhost/myapp-scp  v1          5dd467fce50b  2 years ago  787 kB

Remote host copies use the same HOST:: suffix — for example podman image scp myimage:latest user@server:: pushes from local storage to the remote host's default Podman socket. Rootful-to-rootless on one machine follows the documented sudo podman image scp root@localhost::IMAGE user@localhost:: pattern from the man page.


Compare podman image scp and registry push

podman image scp Registry push
Path Direct host-to-host over SSH Central registry API
Setup SSH access to the destination Running registry + login
Scale Few hosts, ad hoc Many consumers, CI/CD
Distribution model Archive transfer / load Content-addressed registry blobs
Digest workflow Not a substitute for registry promotion Normal production path

Because image scp uses save/transfer/load semantics, it is a poor fit when you need exact registry blob promotion or a single canonical digest across environments. Use a registry when anything beyond a one-off copy depends on that image.


Troubleshooting

Symptom Likely cause Fix
manifest unknown after push or pull Wrong repository path or tag does not exist on that registry Double-check hostname, port, project path, and tag spelling
Pushed the wrong image Source reference typo podman images; verify the first argument to podman push
Push OK but deployment unchanged Mutable tag; node did not re-pull Pin @sha256:... from --digestfile or enforce pull-on-deploy in the orchestrator

References


Summary

podman push publishes a local image to a registry when the destination reference includes the registry hostname. You can tag first or pass source and destination on one command; both paths upload the same layers. Log in before pushing to private registries, Quay, or Docker Hub, and keep TLS verification on everywhere except deliberate lab setups.

--digestfile captures the manifest digest the registry stored — the hook for immutable deploy references — while --retry and --retry-delay smooth over transient failures that are not auth or trust problems. Manifest lists go up with podman manifest push, and verifying with podman pull confirms the tag resolves on the far side.

When SSH is available but a registry is not, podman image scp transfers an image between Podman stores by save/load semantics, including rootless-to-rootful copies on one host. For anything that looks like production distribution, prefer registry push so consumers share one canonical repository and digest story.


Frequently Asked Questions

1. Do I need to run podman tag before podman push?

No. podman push accepts a source image and a separate destination reference, so you can publish without creating another local tag. Tag first when you want the remote name to remain in local storage as a convenient reference.

2. What does podman push --digestfile record?

After a successful push, --digestfile writes the digest of the manifest that landed on the registry. Pipelines use that sha256 value for immutable deployment references such as registry.example.com/project/myapp@sha256:... without implying the tag itself became immutable.

3. When should I use podman image scp instead of pushing to a registry?

Use image scp for one-off or lab transfers when no shared registry exists — including same-host rootful/rootless copies that do not require sshd. Use a registry when many consumers need the same image, you want centralized versioning, or your deployment pipeline already pulls from a registry.

4. Why does podman push say authentication required or unauthorized?

The registry rejected credentials or you are not logged in to that host. Run podman login for the registry, confirm write permission on the repository path, and use the same auth store rootless and rootful Podman read — they are separate unless you point both at the same --authfile.

5. My push succeeded but the server still runs the old image — why?

Tags are mutable pointers. A node that already pulled myapp:v1 may keep the old layers until something triggers a fresh pull. Pin by digest in deployment config or enforce an image pull policy that always re-resolves the tag.
Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with more than 15 years of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive experience, he excels across development, DevOps, networking, and security, delivering robust and efficient solutions for diverse projects.

  • Go (programming language)
  • Python (programming language)
  • DevOps
  • Computer Security
  • Cloud Computing
  • Kubernetes
  • Linux
  • Ansible (software)