<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Oci on Backend Engineering Strategy Tools</title><link>https://backend-engineering-strategy-tools.github.io/site/tags/oci/</link><description>Recent content in Oci on Backend Engineering Strategy Tools</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><lastBuildDate>Mon, 01 Jan 2024 00:00:00 +0000</lastBuildDate><atom:link href="https://backend-engineering-strategy-tools.github.io/site/tags/oci/index.xml" rel="self" type="application/rss+xml"/><item><title>Docker &amp; OCI</title><link>https://backend-engineering-strategy-tools.github.io/site/public-notes/frameworks-tools/docker/</link><pubDate>Mon, 01 Jan 2024 00:00:00 +0000</pubDate><guid>https://backend-engineering-strategy-tools.github.io/site/public-notes/frameworks-tools/docker/</guid><description>&lt;p&gt;Docker packages applications and their dependencies into portable, reproducible units called containers. Unlike virtual machines, containers share the host kernel — they&amp;rsquo;re isolated processes, not emulated hardware. This makes them fast to start, light on resources, and consistent across environments: the same image runs on a developer&amp;rsquo;s laptop, in CI, and in production.&lt;/p&gt;
&lt;p&gt;Docker popularised containers, but the underlying standard is now open. The &lt;strong&gt;OCI (Open Container Initiative)&lt;/strong&gt; defines three specifications:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Image spec&lt;/strong&gt; — the format of a container image: layers, config, manifest&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Runtime spec&lt;/strong&gt; — how a container is run: namespaces, cgroups, lifecycle&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Distribution spec&lt;/strong&gt; — how images are pushed and pulled from registries&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Any tool that produces an OCI image can run on any OCI-compliant runtime. Docker is one implementation. It is still the most natural entry point and the &lt;code&gt;docker&lt;/code&gt; CLI remains the most familiar interface, but it is worth knowing that the ecosystem is broader than Docker Inc.&lt;/p&gt;
&lt;h2 id="oci-images-and-containers"&gt;OCI images and containers
&lt;/h2&gt;&lt;p&gt;An &lt;strong&gt;image&lt;/strong&gt; is a read-only, layered filesystem snapshot built from a Dockerfile — each layer is a diff on top of the previous one. A &lt;strong&gt;container&lt;/strong&gt; is a running instance of an image — an isolated process with its own filesystem, network interface, and process space, sharing the host kernel.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker build -t myapp:1.0 . &lt;span style="color:#75715e"&gt;# build OCI image from Dockerfile&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker run -p 8080:8080 myapp:1.0 &lt;span style="color:#75715e"&gt;# start container&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker ps &lt;span style="color:#75715e"&gt;# list running containers&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker exec -it &amp;lt;id&amp;gt; bash &lt;span style="color:#75715e"&gt;# shell into a running container&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Images are stored in registries — Docker Hub, GitHub Container Registry, ECR, Nexus. All speak the OCI distribution spec, so images built with any tool push and pull the same way.&lt;/p&gt;
&lt;h2 id="dockerfile"&gt;Dockerfile
&lt;/h2&gt;&lt;p&gt;The Dockerfile defines how an image is built — each instruction adds a layer:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-dockerfile" data-lang="dockerfile"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;FROM&lt;/span&gt; &lt;span style="color:#e6db74"&gt;golang:1.22-alpine&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;AS&lt;/span&gt; &lt;span style="color:#e6db74"&gt;build&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;WORKDIR&lt;/span&gt; &lt;span style="color:#e6db74"&gt;/app&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;COPY&lt;/span&gt; go.mod go.sum ./&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;RUN&lt;/span&gt; go mod download&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;COPY&lt;/span&gt; . .&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;RUN&lt;/span&gt; go build -o /app/server .&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;FROM&lt;/span&gt; &lt;span style="color:#e6db74"&gt;alpine:3.19&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;COPY&lt;/span&gt; --from&lt;span style="color:#f92672"&gt;=&lt;/span&gt;build /app/server /server&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;EXPOSE&lt;/span&gt; &lt;span style="color:#e6db74"&gt;8080&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;ENTRYPOINT&lt;/span&gt; [&lt;span style="color:#e6db74"&gt;&amp;#34;/server&amp;#34;&lt;/span&gt;]&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Multi-stage builds&lt;/strong&gt; keep the final image lean: the first stage compiles using the full toolchain, the second copies only the binary. No compiler, no source, no build cache in the image you ship.&lt;/p&gt;
&lt;p&gt;Order matters for layer caching — put things that change rarely (dependency downloads) before things that change often (source code). A cache miss invalidates all subsequent layers.&lt;/p&gt;
&lt;h2 id="volumes-and-bind-mounts"&gt;Volumes and bind mounts
&lt;/h2&gt;&lt;p&gt;Containers have ephemeral filesystems — anything written inside is lost when the container stops. Persist data with volumes:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker volume create pgdata
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker run -v pgdata:/var/lib/postgresql/data postgres:16
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;For local development, bind mounts map a host directory into the container:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker run -v &lt;span style="color:#66d9ef"&gt;$(&lt;/span&gt;pwd&lt;span style="color:#66d9ef"&gt;)&lt;/span&gt;:/app -w /app node:20 npm test
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="networking"&gt;Networking
&lt;/h2&gt;&lt;p&gt;Containers on the same Docker network can reach each other by name. Docker Compose creates a default network automatically; named networks can be created explicitly:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker network create backend
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker run --network backend --name db postgres:16
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker run --network backend myapp &lt;span style="color:#75715e"&gt;# can reach &amp;#39;db&amp;#39; by hostname&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="podman"&gt;Podman
&lt;/h2&gt;&lt;p&gt;Podman is a drop-in Docker replacement that runs without a daemon and without root. The CLI is intentionally compatible:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;alias docker&lt;span style="color:#f92672"&gt;=&lt;/span&gt;podman &lt;span style="color:#75715e"&gt;# usually just works&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Rootless containers mean a compromised container process cannot escalate to host root. Daemonless means no long-running background service with broad system access. On RHEL and Fedora, Podman is the default. For CI environments and security-conscious setups it is the better choice.&lt;/p&gt;
&lt;p&gt;Podman also supports &lt;strong&gt;pods&lt;/strong&gt; — groups of containers sharing a network namespace, mirroring the Kubernetes pod model. Useful for local development that needs to mirror how things will run in the cluster.&lt;/p&gt;
&lt;h2 id="buildah"&gt;Buildah
&lt;/h2&gt;&lt;p&gt;Buildah builds OCI images without a Docker daemon. It can build from a Dockerfile or construct images programmatically using shell commands — useful in CI pipelines where running a privileged Docker daemon is undesirable:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;buildah bud -t myapp:1.0 . &lt;span style="color:#75715e"&gt;# build from Dockerfile&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;buildah push myapp:1.0 registry/myapp:1.0
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Buildah and Podman share the same underlying storage, so images built with Buildah are immediately available to Podman.&lt;/p&gt;
&lt;h2 id="docker-compose"&gt;Docker Compose
&lt;/h2&gt;&lt;p&gt;Compose manages multi-container applications defined in &lt;code&gt;compose.yml&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;services&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;app&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;build&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;ports&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#e6db74"&gt;&amp;#34;8080:8080&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;environment&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;DATABASE_URL&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;postgres://app:secret@db/appdb&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;depends_on&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;db&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;db&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;image&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;postgres:16&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;volumes&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;pgdata:/var/lib/postgresql/data&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;environment&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;POSTGRES_PASSWORD&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;secret&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;volumes&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;pgdata&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker compose up -d &lt;span style="color:#75715e"&gt;# start in background&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker compose logs -f &lt;span style="color:#75715e"&gt;# stream logs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker compose down &lt;span style="color:#75715e"&gt;# stop and remove containers&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Compose is useful for local development environments. It is a shame it exists as a separate abstraction — it taught people to think in multi-container terms without teaching them Kubernetes, and then left them with a gap to cross when they needed to go to production. That said, it is practical for what it does and is not going away.&lt;/p&gt;
&lt;p&gt;For production orchestration, see &lt;a class="link" href="../../kubernetes/kubernetes/" &gt;Kubernetes&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="skopeo"&gt;Skopeo
&lt;/h2&gt;&lt;p&gt;Skopeo works with OCI images directly — copy, inspect, and convert — without pulling them to local storage. Useful in pipelines and for auditing registries:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Inspect an image without pulling it&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;skopeo inspect docker://registry.example.com/myapp:1.0
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Copy between registries without touching local disk&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;skopeo copy docker://source-registry/myapp:1.0 docker://dest-registry/myapp:1.0
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Copy to a local OCI layout&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;skopeo copy docker://myapp:1.0 oci:myapp-local:1.0
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;skopeo inspect&lt;/code&gt; is particularly useful for checking image metadata, digest, and labels in CI before deciding whether to promote an image.&lt;/p&gt;
&lt;h2 id="oras"&gt;ORAS
&lt;/h2&gt;&lt;p&gt;ORAS (OCI Registry As Storage) pushes and pulls arbitrary artifacts to OCI registries — not just container images. Helm charts, SBOMs, attestations, Terraform modules, binary releases — anything can be stored in a registry that speaks OCI distribution spec:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Push a file as an OCI artifact&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;oras push registry.example.com/myapp-sbom:1.0 sbom.json:application/spdx+json
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Pull it back&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;oras pull registry.example.com/myapp-sbom:1.0
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This matters because it means a single registry can become the distribution mechanism for the entire software supply chain — image, SBOM, signature, attestation — all with the same access controls and audit trail.&lt;/p&gt;
&lt;h2 id="useful-practices"&gt;Useful practices
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;Use specific image tags (&lt;code&gt;postgres:16.2&lt;/code&gt;, not &lt;code&gt;postgres:latest&lt;/code&gt;) — &lt;code&gt;latest&lt;/code&gt; changes under you&lt;/li&gt;
&lt;li&gt;Reference images by digest in production (&lt;code&gt;myapp@sha256:abc123&lt;/code&gt;) — tags are mutable, digests are not&lt;/li&gt;
&lt;li&gt;Run as a non-root user: &lt;code&gt;USER appuser&lt;/code&gt; in the Dockerfile&lt;/li&gt;
&lt;li&gt;Add a &lt;code&gt;.dockerignore&lt;/code&gt; to exclude &lt;code&gt;.git&lt;/code&gt;, &lt;code&gt;node_modules&lt;/code&gt;, build artefacts from the build context&lt;/li&gt;
&lt;li&gt;Keep images small — large images are slow to push, pull, and scan&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="resources"&gt;Resources
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://opencontainers.org/" target="_blank" rel="noopener"
 &gt;OCI specifications&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://docs.docker.com/" target="_blank" rel="noopener"
 &gt;Docker documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://podman.io/docs" target="_blank" rel="noopener"
 &gt;Podman documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://buildah.io/" target="_blank" rel="noopener"
 &gt;Buildah documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/containers/skopeo" target="_blank" rel="noopener"
 &gt;Skopeo GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://oras.land/docs/" target="_blank" rel="noopener"
 &gt;ORAS documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item></channel></rss>