You need to move an image without a daemon
You are standing in front of a freshly provisioned Fedora Server instance. The network cable is unplugged for security reasons, or your corporate firewall blocks outbound registry traffic. You need to get a container image onto this machine without spinning up a full container runtime, pulling it into a local daemon, and hoping the layers survive the transfer. You also need to verify the image digest and check its labels before it ever touches your production environment. skopeo is the tool for this exact situation.
What skopeo actually does
Container images are not single files. They are collections of compressed layers, configuration metadata, and manifest files hosted on remote registries. Traditional tools like podman pull or docker pull require a running daemon or a local storage backend to unpack, cache, and manage those layers. That overhead is fine for running containers. It is unnecessary when you only want to move or inspect an image.
skopeo operates at the transport layer. It speaks directly to registry APIs, reads OCI-compliant archives, and writes to local directories or storage backends without ever starting a container engine. Think of it as a specialized file transfer protocol that understands container image formats. It reads the manifest, downloads the layers, and writes them to your target location. No daemon. No background processes. No local image cache pollution.
Run skopeo inspect first. Verify the digest before you trust the payload.
Install and configure the tool
Fedora ships skopeo in the default repositories. The package pulls in the necessary container libraries and authentication helpers. Install it with dnf. If you plan to work with private registries, you will need to store your credentials. skopeo reads authentication data from ~/.config/containers/auth.json by default. You can generate this file by running skopeo login or by copying your existing Podman authentication file.
# Install the skopeo package and its dependencies
sudo dnf install skopeo -y
# WHY: -y skips the confirmation prompt for automated or quick setups
# Authenticate to a private registry and write credentials to the default auth file
skopeo login registry.example.com
# WHY: Stores encrypted credentials in ~/.config/containers/auth.json for subsequent commands
Fedora's container ecosystem defaults to storing configuration in ~/.config/containers/. Files in /etc/containers/ apply system-wide. Edit the user directory for personal workflows. Edit the system directory only when managing a shared server. Keep your auth files out of version control.
Run skopeo login once per registry. The credential helper handles the rest.
Inspect an image before you pull it
Inspection is the first step in any responsible container workflow. You need to know the operating system base, the architecture, the labels, and the exact digest before you copy the image to your infrastructure. skopeo inspect queries the registry manifest and returns a JSON payload. You can pipe it to jq for readable output, or read the raw JSON directly.
# Query the remote registry for image metadata without downloading layers
skopeo inspect docker://quay.io/podman/stable:latest
# WHY: docker:// transport scheme tells skopeo to use the Docker Registry HTTP API V2
# Filter the output to show only the architecture and OS fields
skopeo inspect docker://quay.io/podman/stable:latest | jq '.Architecture, .Os'
# WHY: jq parses the JSON response and extracts specific keys for quick verification
The output includes the Digest field. That hash is the cryptographic fingerprint of the image manifest. Pin your deployments to the digest, not the tag. Tags are mutable. Digests are not. If the digest changes between your inspection and your copy, someone pushed a new image under the same tag. Verify the hash before proceeding.
Check the digest before you copy. Mutable tags cause silent production breakage.
Copy images between transports
skopeo copy moves images between any two supported transport schemes. The source and destination can be remote registries, local OCI archives, directory trees, or the native containers-storage backend. The command reads the source manifest, downloads the layers, and writes them to the destination format. It handles compression, deduplication, and manifest rewriting automatically.
# Download a remote image and pack it into a single portable OCI archive file
skopeo copy docker://quay.io/podman/stable:latest oci-archive:./podman-stable.tar
# WHY: oci-archive: writes a self-contained tarball compliant with the OCI Image Layout spec
# Transfer an image from a public registry to a private internal registry
skopeo copy docker://quay.io/podman/stable:latest docker://registry.internal.io/library/podman:stable
# WHY: Both sides use docker:// so skopeo streams layers directly between registry APIs
# Copy a remote image into the local containers-storage backend for Podman to use
skopeo copy docker://quay.io/podman/stable:latest containers-storage:localhost/podman:stable
# WHY: containers-storage: writes to the default Fedora storage driver without requiring podman pull
The oci-archive transport creates a single tar file containing the manifest, config, and all layers. It is ideal for air-gapped transfers, version control, or backup pipelines. The containers-storage transport writes directly to the storage backend that Podman uses. You can run the container immediately after the copy finishes without an extra import step. The dir transport writes files to a local folder, which is useful for debugging or manual layer inspection.
Match the transport to your workflow. OCI archives for portability. Containers-storage for immediate local use.
Verify the transfer
Verification is non-negotiable in container workflows. A corrupted layer or a mismatched manifest will fail silently until runtime. After copying an image, run skopeo inspect against the destination transport. Compare the digest from the source inspection with the destination inspection. The hashes must match exactly.
# Inspect the local OCI archive to confirm the manifest and digest survived the copy
skopeo inspect oci-archive:./podman-stable.tar
# WHY: Reads the OCI layout metadata directly from the tarball without extracting it
# Verify the file size matches expectations and check the archive integrity
tar -tf ./podman-stable.tar | head -n 5
# WHY: Lists the top-level OCI layout files to confirm the archive structure is valid
If you copied to containers-storage, you can verify the image exists in the local backend. Run podman images to see it listed. The repository and tag should match your destination specification. If the image does not appear, check your storage driver configuration. Fedora defaults to overlay with fuse-overlayfs or native depending on the kernel version. Mismatched storage drivers cause silent copy failures.
Run the digest comparison before you deploy. A single corrupted layer breaks the entire container.
Common pitfalls and error messages
Authentication failures are the most frequent issue. skopeo will refuse to pull from private registries without valid credentials. The error is explicit.
Error: initializing source docker://registry.internal.io/library/podman:stable: reading manifest stable in registry.internal.io/library/podman: unauthorized: authentication required
The fix is straightforward. Run skopeo login registry.internal.io and provide valid credentials. If you are using a credential helper that stores tokens in a keyring, ensure the helper is installed and functional. Fedora uses secret-tool by default. Install gnome-keyring or kwallet if you are running a headless server without a desktop session.
Network timeouts occur with large images or slow connections. skopeo does not resume interrupted downloads by default. If the transfer fails halfway, delete the partial destination and retry. Add the --retry-times flag if your network is unstable.
SELinux denials appear when you copy images to restricted directories or run skopeo with mismatched contexts. Check the audit log before disabling the policy.
# Check for SELinux denials related to container storage operations
journalctl -t setroubleshoot | tail -n 10
# WHY: Filters the journal for SELinux troubleshooting messages that explain the denial
Tag confusion causes silent version drift. Registries treat tags as mutable pointers. Two different images can share the latest tag over time. Always inspect the digest. Pin your copy commands to the digest when reproducibility matters. Use docker://quay.io/podman/stable@sha256:abc123... instead of docker://quay.io/podman/stable:latest.
Read the journal before guessing. The error message tells you exactly which transport or credential step failed.
When to use skopeo versus other tools
Use skopeo when you need to inspect or move container images without a running container daemon. Use podman pull when you intend to run the container immediately and want the image cached in the local storage backend. Use crane when you are writing Go applications that need programmatic access to registry APIs. Use docker when you are maintaining legacy infrastructure that requires the Docker daemon and socket. Stay on skopeo for CI pipelines, air-gapped deployments, and registry migration tasks.
Pin to digests in production. Tags are for development convenience.