| Tested on | Rocky Linux 10.2 (Red Quartz) workstation |
|---|---|
| Package | podman 5.8.2kubectl 1.36.3 |
| Applies to | Any Linux host with Podman or Docker; any Kubernetes cluster |
| Cert prep | CKAD · CKS |
| Lab environment | Multi-node kubeadm cluster with containerd — install Kubernetes with kubeadm |
| Privilege | Normal user for Podman and kubectl; sudo may be required to install Podman or configure node registry trust |
| Scope | Containerfile basics, build, tag, push, ENTRYPOINT and CMD, multi-stage builds, image tags and digests, imagePullPolicy, deploying a custom image, optional commit patch, and preload with ctr import. Does not cover registry installation, imagePullSecrets, full ImagePullBackOff remediation, scanning, signing, or a full image-optimization guide. |
| Related guides | Podman command cheat sheet Deployments and rolling updates Kubernetes pods |
Kubernetes runs containers from images you publish to a registry—it does not build them for you. This guide walks through one demo-app nginx image: build on the workstation, push to a registry, deploy from spec.containers[].image, and optionally patch with commit for a quick lab iteration.
Quick workflow
The table below maps the workstation commands in this article. The imagePullPolicy section explains IfNotPresent before you deploy. Deploy and verify steps are in Use the image in Kubernetes.
Replace REGISTRY with a registry-qualified prefix such as docker.io/myuser, quay.io/myorg, or 192.168.56.108:5001 on the lab network.
| Phase | What it does | Podman | Docker (equivalent syntax) |
|---|---|---|---|
| Build | Create an image from a Containerfile |
podman build -t demo-app:1.0.0 . |
docker build -f Containerfile -t demo-app:1.0.0 . |
| Test locally | Confirm the image runs before you push | podman run -d --name demo-test -p 8080:80 localhost/demo-app:1.0.0 then curl |
docker run -d --name demo-test -p 8080:80 demo-app:1.0.0 then curl |
| Tag | Add a registry-qualified name to the local image | podman tag localhost/demo-app:1.0.0 "${IMAGE_REF}" |
docker tag demo-app:1.0.0 "${IMAGE_REF}" |
| Push | Upload the tagged image to a registry nodes can pull | podman push --tls-verify=false "${IMAGE_REF}" |
docker push "${IMAGE_REF}" after insecure-registry config for HTTP labs |
| Deploy | Run the pushed image in the cluster | kubectl apply -f demo-app-deployment.yaml |
Same |
| Verify image | Confirm Pods use ${IMAGE_REF} and the pulled digest |
kubectl get pods -n image-demo -l app=demo-app -o custom-columns=NAME:.metadata.name,IMAGE:.spec.containers[0].image |
Same |
Build the container image
A build reads a Containerfile and produces a local container image stored on your workstation in Podman or Docker image storage. Podman finds Containerfile in the build context automatically. With Docker, pass -f Containerfile.
| Instruction | What it does | Why it matters |
|---|---|---|
FROM |
Choose the base image for the first layer | Sets OS, runtime, and starting size |
WORKDIR |
Set the default directory inside the image | Keeps later COPY and RUN paths predictable |
COPY |
Copy files from the build context into the image | Ships your app files into the image |
RUN |
Execute a command during the build | Installs packages, compiles code, sets permissions |
EXPOSE |
Document a port the container listens on | Documents intent; does not publish the port on the host |
USER |
Run later steps and the default container process as this user | Avoids running as root when not required |
ENTRYPOINT |
Set the main executable for the container | Defines what process starts when the container runs |
CMD |
Provide default arguments to ENTRYPOINT |
Supplies defaults the user or orchestrator can override |
Create a project directory and application file:
mkdir demo-app && cd demo-appCreate and save as index.html:
<!DOCTYPE html>
<html><body><h1>demo-app 1.0.0</h1></body></html>Create Containerfile:
FROM docker.io/library/nginx:1.28.3-alpine
WORKDIR /usr/share/nginx/html
COPY index.html .
RUN chmod 644 index.html
EXPOSE 80
ENTRYPOINT ["nginx"]
CMD ["-g", "daemon off;"]Line by line:
FROMpulls a small nginx base image that already includes the web server binary.WORKDIRsets the directory nginx serves by default.COPYplaces yourindex.htmlinto that directory at build time.RUN chmod 644 index.htmlsets file permissions inside the image during the build. EveryRUNcreates a new image layer.EXPOSE 80documents that the container listens on port 80.ENTRYPOINT ["nginx"]starts nginx as the main process.CMD ["-g", "daemon off;"]keeps nginx in the foreground so the container stays running.
Create .containerignore when you build with Podman. When you use Docker, save the same patterns in .dockerignore. Docker reads .dockerignore to exclude files from the build context. Podman reads .containerignore (and also accepts .dockerignore).
.git
*.log
README.mdDo not put these in the build context:
- Registry passwords or API tokens
kubeconfigfiles- Private keys or TLS material you only need at runtime
In a Pod spec:
commandreplaces DockerENTRYPOINTargsreplaces DockerCMD
For example, command: ["nginx"] and args: ["-g", "daemon off;"] mirror the Containerfile. For overrides, probes, and environment wiring, see Commands, args and environment variables.
Build from a Containerfile
Your project directory should look like this before you build:
ls -latotal 12
drwxr-xr-x. 2 user user 4096 Jul 25 19:00 .
drwxr-xr-x. 8 user user 4096 Jul 25 18:55 ..
-rw-r--r--. 1 user user 24 Jul 25 18:56 .containerignore
-rw-r--r--. 1 user user 186 Jul 25 18:58 Containerfile
-rw-r--r--. 1 user user 58 Jul 25 18:57 index.htmlWith Docker, use .dockerignore instead of .containerignore and keep the same patterns.
Run the build from that directory:
docker build -f Containerfile -t demo-app:1.0.0 .
podman build -t demo-app:1.0.0 .-t(--tag) names the image asrepository:tag— heredemo-appis the image name and1.0.0is the tag.is the build context. TheCOPY index.html .instruction readsindex.htmlfrom this directory.
With Docker, pass -f Containerfile because Docker does not look for that filename by default.
The result is a local image on your workstation. Podman lists it as localhost/demo-app:1.0.0. The next section runs it locally before you tag and push.
Sample output (trimmed):
STEP 1/7: FROM docker.io/library/nginx:1.28.3-alpine
...
Successfully tagged localhost/demo-app:1.0.0List matching images and confirm architecture before you push to amd64 cluster nodes:
podman images demo-app
podman image inspect --format '{{.Id}} {{.Architecture}}' localhost/demo-app:1.0.0Sample output:
REPOSITORY TAG IMAGE ID CREATED SIZE
localhost/demo-app 1.0.0 1f4c09d19c7b 1 second ago 63.5 MBSample output from podman image inspect (trimmed):
1f4c09d19c7bebce6aad4caf29fed6c0b8f4e03bfb7487c0d2238a1f660b3a7f amd64That value is the local image ID ({{.Id}}). It is not the registry manifest digest you get from podman push --digestfile.
Docker equivalents: docker images demo-app and docker image inspect.
Test the image locally
Before you tag and push, confirm the image runs on your workstation. A quick local run catches wrong ENTRYPOINT/CMD or missing files before anything reaches the registry.
Publish port 80 from the container to a host port:
podman run -d --name demo-test -p 8080:80 localhost/demo-app:1.0.0Docker equivalent:
docker run -d --name demo-test -p 8080:80 demo-app:1.0.0Request the page with the curl command:
curl -s http://127.0.0.1:8080/Sample output:
<!DOCTYPE html>
<html><body><h1>demo-app 1.0.0</h1></body></html>That response matches the index.html you copied in the Containerfile. Stop and remove the test container when you finish:
podman stop demo-testpodman rm demo-testDocker equivalents: docker stop demo-test and docker rm demo-test.
Tag the image
A tag is the version label after the colon in an image reference, such as 1.0.0 in demo-app:1.0.0. Tags are human-readable pointers. They help you and Kubernetes refer to a specific build without typing the full image ID.
Before you push, the image exists only under a local name such as localhost/demo-app:1.0.0. Tagging adds another name that includes the registry hostname and repository path. It does not duplicate image layers on disk; it creates an additional reference to the same local image.
Set the registry variables once. You reuse ${IMAGE_REF} when you push and in the Kubernetes manifest:
REGISTRY="192.168.56.108:5001"
IMAGE_REF="${REGISTRY}/demo-app:1.0.0"A full registry reference looks like this:
${IMAGE_REF}Expanded on the lab network:
192.168.56.108:5001/demo-app:1.0.0| Part | Example | Purpose |
|---|---|---|
| Registry host | registry.example.com or 192.168.56.108:5001 |
Where nodes pull the image |
| Repository path | team/demo-app or demo-app |
Groups related images in the registry |
| Tag | 1.0.0 |
Identifies this build or release |
Tag the built image for the lab registry:
podman tag localhost/demo-app:1.0.0 "${IMAGE_REF}"Docker equivalent:
docker tag demo-app:1.0.0 "${IMAGE_REF}"Prefer 1.0.0, 1.0.1, or a Git commit id over latest. latest is a normal tag, not a promise of recency, and Kubernetes treats it differently for default pull policy.
Do not confuse these three identifiers:
- Tag — movable label such as
1.0.0in${IMAGE_REF} - Local image ID — short hash from
podman images; unique to each build on your workstation - Registry digest — immutable manifest hash from
podman push --digestfile
Capture the registry digest when you push in the next section if you need an exact, pinned reference.
Reference by digest in a manifest when you need immutability:
image: ${REGISTRY}/demo-app@sha256:1b2df623153d38d6845a4b71c3d55bc6347a7212365fdd6ffa326b1c13a79e57Push the image to a registry
A registry is the shared store cluster nodes pull from. Pushing uploads the tagged ${IMAGE_REF} image so worker nodes can download the same layers when a Pod starts.
Logging in on your workstation does not authenticate cluster nodes. Nodes pull with their own credentials or anonymous access rules.
- Lab HTTP registry (
192.168.56.108:5001): no login; usepodman push --tls-verify=falseand configure node trust — see Private registry and imagePullSecrets - HTTPS registry (Docker Hub, Quay.io): run
podman loginfirst; omit--tls-verify=falseon push
For Docker Hub, Quay.io, or another authenticated private registry:
podman login quay.ioDocker equivalent: docker login quay.io.
Push the tagged image and capture the manifest digest:
podman push --tls-verify=false --digestfile=demo-app.digest "${IMAGE_REF}"For HTTPS registries such as Docker Hub or Quay.io, omit --tls-verify=false.
Docker equivalent for HTTPS registries:
docker push "${IMAGE_REF}"For the HTTP lab registry on port 5001, docker push "${IMAGE_REF}" works only after you configure Docker Engine to allow 192.168.56.108:5001 as an insecure registry. Docker does not support Podman's --tls-verify=false flag on docker push.
Sample output (trimmed):
Copying blob sha256:a2738b08d711...
Copying config sha256:1f4c09d19c7b...
Writing manifest to image destinationRead the digest and build a digest-pinned reference:
DIGEST=$(cat demo-app.digest)
echo "${REGISTRY}/demo-app@${DIGEST}"Sample output:
192.168.56.108:5001/demo-app@sha256:1b2df623153d38d6845a4b71c3d55bc6347a7212365fdd6ffa326b1c13a79e57List tags through the registry API to confirm the push:
curl -s "http://${REGISTRY}/v2/demo-app/tags/list"Sample output:
{"name":"demo-app","tags":["1.0.0"]}For registry setup, authentication, and imagePullSecrets, use Private registry and imagePullSecrets instead of repeating that workflow here.
imagePullPolicy
imagePullPolicy tells the kubelet whether to contact the registry when a Pod starts, or to use a copy already cached on the node. The Deployment below sets imagePullPolicy: IfNotPresent.
For IfNotPresent, the kubelet checks the node cache first:
- Cache found — start the Pod from the local copy; no registry contact
- Cache miss — pull from the registry, then start the Pod
With Always, the kubelet asks the container runtime to resolve the image name through the registry whenever the container starts. If the resolved image layers already exist locally, the runtime can reuse them instead of downloading them again. Never uses only the local copy and does not contact the registry.
| Policy | What the kubelet does | When to use it |
|---|---|---|
Always |
Resolves the image through the registry on every container start; reuses local layers when they already match | Moving tags such as latest you want re-resolved |
IfNotPresent |
Pulls only when the image is not already cached on the node | Version tags such as 1.0.0 on multi-node clusters |
Never |
Does not pull; the image must already exist on the node | Preloaded or ctr-imported images on the node |
When imagePullPolicy is omitted, Kubernetes sets a default when the Pod template is first created:
- Image tag
latestor no tag:Always - Other tags:
IfNotPresent - Image referenced by digest:
IfNotPresent
Changing the image tag on an existing Deployment does not automatically change a stored imagePullPolicy. Set the policy explicitly in course examples so behaviour stays visible.
Versioned tags such as 1.0.0 pair naturally with IfNotPresent on nodes that already cached the image. Use Always when you intentionally re-resolve a moving tag. For pull failures and private registry auth, see Private registry and imagePullSecrets.
Use the image in Kubernetes
After ${IMAGE_REF} is in the registry, reference that same value from spec.containers[].image in a Pod or Deployment manifest. The kubelet on each scheduled node pulls the image (according to imagePullPolicy) and starts the container.
Save demo-app-deployment.yaml with the image reference you pushed:
cat > demo-app-deployment.yaml <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: demo-app
namespace: image-demo
spec:
replicas: 2
selector:
matchLabels:
app: demo-app
template:
metadata:
labels:
app: demo-app
spec:
containers:
- name: demo-app
image: ${IMAGE_REF}
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
EOFThe image field uses the same ${IMAGE_REF} you tagged and pushed (192.168.56.108:5001/demo-app:1.0.0 on the lab network).
containerPort documents the port for Services and some health checks. Omitting it does not stop a process from listening inside the container.
Create the namespace and apply the manifest:
kubectl create namespace image-demo
kubectl apply -f demo-app-deployment.yaml
kubectl rollout status deployment/demo-app -n image-demo --timeout=120skubectl apply creates the Deployment, ReplicaSet, and Pods. kubectl rollout status waits until the Pods are ready so the next checks do not show Pending or ContainerCreating.
Confirm the Pods are Running and reference the image you pushed. Check these in order:
IMAGEcolumn matches${IMAGE_REF}imageIDshows the registry digest the kubelet pulledcurlconfirms the built file responds (below)
kubectl get pods -n image-demo -l app=demo-app -o custom-columns=NAME:.metadata.name,IMAGE:.spec.containers[0].image,STATUS:.status.phase,NODE:.spec.nodeNameSample output:
NAME IMAGE STATUS NODE
demo-app-7c4fffd5b5-bpr58 192.168.56.108:5001/demo-app:1.0.0 Running worker01
demo-app-7c4fffd5b5-jx8cp 192.168.56.108:5001/demo-app:1.0.0 Running worker01The IMAGE column should match ${IMAGE_REF}. To see the digest the kubelet resolved at runtime:
kubectl get pods -n image-demo -l app=demo-app -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.containerStatuses[0].imageID}{"\n"}{end}'Sample output (trimmed):
demo-app-7c4fffd5b5-bpr58 192.168.56.108:5001/demo-app@sha256:1b2df623153d...
demo-app-7c4fffd5b5-jx8cp 192.168.56.108:5001/demo-app@sha256:1b2df623153d...Both Pods should report the same imageID digest. That confirms the node pulled the manifest you pushed, not a different local tag.
In this lab run both Pods landed on worker01. Every node that may run this workload must be able to pull ${IMAGE_REF}.
Verify the image serves your files
Forward a local port to the Deployment and request the page from your build:
kubectl port-forward -n image-demo deployment/demo-app 8080:80In another terminal:
curl -s http://127.0.0.1:8080/Sample output:
<!DOCTYPE html>
<html><body><h1>demo-app 1.0.0</h1></body></html>That response comes from index.html in your Containerfile build. Together with the IMAGE and imageID checks above, that confirms the cluster is running the image you pushed. Stop port-forward with Ctrl+C when you finish.
Build with multiple stages
This section is optional. The demo-app nginx walkthrough above is already a single-stage build. Use multiple stages when you compile code or need build tools that should not ship in the final image.
Do not confuse stages with the steps Podman prints during a build (STEP 1/7, STEP 2/7, and so on). Each line in a Containerfile is one instruction and one build step. A stage starts only when you add another FROM instruction.
| Term | What it means | Example in this article |
|---|---|---|
| Step | One instruction in the Containerfile |
COPY, RUN, EXPOSE — the nginx demo has 7 steps in one stage |
| Stage | One FROM block through the next FROM (or end of file) |
The Go multi-stage example has 2 stages: builder and the final Alpine stage |
| Layer | A filesystem change stored in the image | Each RUN, COPY, and ADD usually adds a layer |
A single-stage build has one FROM. Every package you install with RUN stays in the final image you push to production, even when you only needed those tools during the build.
A multi-stage build has two or more FROM instructions. Earlier stages can compile code or prepare files. The final stage becomes the image you tag and push. Use COPY --from=builder (or --from=0) to copy only the files you need. Compilers, headers, and package managers stay in the builder stage and do not ship to the cluster.
Combining commands in one RUN with && reduces layers inside a stage. It does not create or remove stages. Multi-stage builds and fewer layers solve different problems.
| Approach | Final image contains | Best for |
|---|---|---|
| Single-stage | Runtime plus every build-time package | Quick demos and simple static sites |
| Multi-stage | Only the last stage (runtime files you copied in) | Compiled apps and smaller production images |
Compare image size with a compiled app
The nginx demo-app above is already small. To see a large size gap, compare a Go binary built in a full toolchain image against a multi-stage copy into Alpine.
Save main.go:
package main
import "fmt"
func main() { fmt.Println("demo-app") }Single-stage build (toolchain stays in the final image):
FROM docker.io/library/golang:1.23-bookworm
WORKDIR /src
COPY main.go .
RUN go mod init demo && go build -o /demo-app .
CMD ["/demo-app"]Save as Containerfile.go-single and build:
podman build -f Containerfile.go-single -t demo-go-single:1.0.0 .Multi-stage build (two stages: compile in builder, ship only the binary in the final Alpine stage):
FROM docker.io/library/golang:1.23-bookworm AS builder
WORKDIR /src
COPY main.go .
RUN go mod init demo && go build -o /demo-app .
FROM docker.io/library/alpine:3.21
COPY --from=builder /demo-app /demo-app
CMD ["/demo-app"]Save as Containerfile.go-multistage and build:
podman build -f Containerfile.go-multistage -t demo-go-multistage:1.0.0 .Compare sizes:
podman images --format '{{.Repository}}:{{.Tag}} {{.Size}}' | grep -E 'demo-go-single|demo-go-multistage'Sample output:
localhost/demo-go-multistage:1.0.0 10.3 MB
localhost/demo-go-single:1.0.0 895 MBThe single-stage image keeps the entire Go compiler and Debian base. The multi-stage image has only two stages; Podman may print more than two step numbers because it counts instructions in each stage. The final image copies only the static binary into Alpine.
Modify an existing image and commit
The main path in this article is Containerfile → build → tag → push → deploy. This section continues with the same localhost/demo-app:1.0.0 image you built and deployed above. Use podman commit (or docker commit) only when you need a quick lab patch and cannot rebuild from a Containerfile right away.
podman run starts a container from a local image. The image layers stay read-only; changes you make inside the running container live in a thin writable layer on top. commit saves that writable layer as a new image tag. Stopping or removing the container does not change the committed image.
Start a container you can edit
Start from the same local image you built in Build from a Containerfile. Run nginx with its default ENTRYPOINT and CMD so the committed image still starts the web server normally:
podman run -d --name demo-edit localhost/demo-app:1.0.0Docker equivalent:
docker run -d --name demo-edit demo-app:1.0.0Runtime --entrypoint replaces the image entrypoint and clears its default command. A commit can preserve those overrides in the new image, so avoid them when you want nginx to start as defined in the Containerfile.
Images without a shell (for example scratch or distroless) cannot be patched this way. Rebuild from a Containerfile or use a debug stage in a multi-stage build.
Patch files inside the container
Attach an interactive shell:
podman exec -it demo-edit shDocker equivalent: docker exec -it demo-edit sh.
Alpine-based images ship sh, not always bash. Use bash only when the image includes it.
Patch a file from the host without an interactive session:
podman exec demo-edit sh -c 'echo patched > /usr/share/nginx/html/patch.txt'If the image runs as a non-root user and you need root to edit system paths, add --user 0 to podman run or podman exec:
podman exec -u 0 -it demo-edit shCommit, then stop and remove the container
Save the writable layer as a new local image:
podman commit demo-edit localhost/demo-app:1.0.1Docker equivalent:
docker commit demo-edit demo-app:1.0.1commit creates the image. It does not stop the editing container. Clean up the container in separate steps:
podman stop demo-edit
podman rm demo-editDocker equivalent:
docker stop demo-edit
docker rm demo-editTag and push the patched image
Tag and push the committed image with a new reference. Keep ${REGISTRY} from earlier and set a separate variable for the patched tag:
PATCH_IMAGE_REF="${REGISTRY}/demo-app:1.0.1"
podman tag localhost/demo-app:1.0.1 "${PATCH_IMAGE_REF}"
podman push --tls-verify=false "${PATCH_IMAGE_REF}"Docker equivalent:
docker tag demo-app:1.0.1 "${PATCH_IMAGE_REF}"
docker push "${PATCH_IMAGE_REF}"Confirm both tags are in the registry:
curl -s "http://${REGISTRY}/v2/demo-app/tags/list"Sample output:
{"name":"demo-app","tags":["1.0.0","1.0.1"]}Redeploy and verify the patch
Update the Deployment to use the patched image:
kubectl set image deployment/demo-app -n image-demo demo-app="${PATCH_IMAGE_REF}"
kubectl rollout status deployment/demo-app -n image-demo --timeout=120sConfirm the Pods now reference 1.0.1:
kubectl get pods -n image-demo -l app=demo-app -o custom-columns=NAME:.metadata.name,IMAGE:.spec.containers[0].image,STATUS:.status.phaseSample output:
NAME IMAGE STATUS
demo-app-774b4b6b8d-6mmzf 192.168.56.108:5001/demo-app:1.0.1 Running
demo-app-774b4b6b8d-7dpm7 192.168.56.108:5001/demo-app:1.0.1 RunningCheck the patched file inside a running Pod:
kubectl exec -n image-demo deploy/demo-app -- cat /usr/share/nginx/html/patch.txtSample output:
patchedThe original index.html from your 1.0.0 build is still present. Only patch.txt was added by the commit step. Prefer updating the Containerfile and rebuilding when you need a repeatable production image.
Preload an image without a registry
The main path in this article is build → tag → push → deploy. When worker nodes cannot reach a registry, for example in an air-gapped lab, export the image on your workstation and import it into containerd on each node. The lab cluster uses containerd, so podman load on a node does not make the image visible to the kubelet.
On your workstation:
podman save -o demo-app-1.0.0.tar "${IMAGE_REF}"Copy the archive to every eligible worker node, then on each containerd node:
sudo ctr -n k8s.io images import demo-app-1.0.0.tar
sudo crictl images | grep demo-appContainerd's Kubernetes CRI plugin uses the k8s.io containerd namespace. Deploy with imagePullPolicy: Never or IfNotPresent so the kubelet uses the imported copy.
Docker equivalents on the workstation: docker save and copy the tar. Import on the node still uses ctr -n k8s.io images import when the cluster runtime is containerd.
Troubleshoot container image problems
Use this table when a build, commit, push, or cluster pull fails.
| Symptom | Likely cause | Fix |
|---|---|---|
| Image builds but the container exits immediately | Wrong ENTRYPOINT/CMD or missing foreground process |
Override entrypoint for debugging; check podman logs |
| Application port is not reachable | Application is not listening on the expected port, or no Kubernetes Service or port-forward is configured | Confirm the process listens inside the container; check Pod events and Service config |
| Incorrect ENTRYPOINT or CMD | Process path or arguments wrong in the image | podman inspect and a local podman run |
| Registry push is denied | Not logged in, wrong repository name, or insufficient token scope | podman login and full IMAGE_REF |
References
- Kubernetes images
- kubectl run a container image
- Podman documentation
- Containerfile syntax reference
- Open Container Initiative image spec
Summary
Build an image from a Containerfile, tag it with a registry-qualified name your nodes can reach, push it, and reference that value in spec.containers[].image. Set imagePullPolicy before you deploy so kubelet behaviour matches how you publish updates: IfNotPresent for explicit version tags from a private registry, Always when the cluster must re-resolve a tag, or IfNotPresent/Never after you preload the image on each node with podman save and ctr -n k8s.io images import.
Verify the running workload with pod IMAGE and imageID columns, then curl or exec against the Service—not rollout status alone. For a quick lab iteration you can podman commit a running container into a new local tag, push it, and redeploy; prefer rebuild-and-push for changes you intend to keep. Registry authentication and repeated pull failures are covered in the private registry and imagePullSecrets guide.

