Skip to main content
Cloud DevOps Solutions
Get a quote Talk to an engineer

Containers and Kubernetes

Docker in production: images you can trust, builds that stay fast

Docker packages an application and its dependencies into an immutable image that runs the same on a laptop, in CI and in production. We help teams write lean, non-root, scanned and signed images, speed up builds, run a private registry and move from Compose to Kubernetes or a managed container service.

Key takeaways

  • Most Docker problems in production come from the image, not the runtime: size, root users, stale base images and secrets baked into layers.
  • Multi-stage builds, pinned base images and layer ordering give the biggest gains in size, security and build time.
  • Scanning and signing in CI turns images into a supply chain you can audit rather than a pile of tags.
  • Docker the build tool and Docker the runtime are separate decisions: Kubernetes no longer needs the Docker daemon on its nodes.
  • Compose is excellent for local development and small deployments, and a poor substitute for a scheduler once you need rollouts and autoscaling.

What teams use Docker for

Standardising images across teams

One base image policy, one Dockerfile template per language and a linter in CI, so services stop drifting apart.

Faster, cacheable CI builds

BuildKit cache mounts, remote cache and sensible layer order so a small code change does not rebuild the world.

Hardening for audit

Non-root users, read-only filesystems, vulnerability scanning and image signing to meet customer security questionnaires.

Reproducible local environments

Docker Compose setups that mirror production dependencies such as databases and queues, so onboarding takes hours rather than days.

Private registry operations

ECR, ACR, GAR or Harbor with lifecycle rules, replication and pull permissions that follow least privilege.

Moving to an orchestrator

Translating Compose services into Kubernetes manifests or Helm charts, or into ECS or Cloud Run when that is the simpler fit.

What Docker is and why teams adopt it

Docker packages an application, its libraries and its runtime settings into an image. A container is a running instance of that image, isolated from its neighbours by Linux namespaces and control groups. The appeal is consistency: the artefact you test is the artefact you deploy, and a new engineer can run the whole stack with one command.

That promise holds only if the image is built well. Our Docker containerisation service exists because most teams adopt Docker quickly, then live with the consequences of early Dockerfiles for years. This page covers how we approach Docker consulting for teams that already ship containers or are about to.

How we build images for production

Multi-stage builds and base images

A production image should contain the application and what it needs to run, nothing else. Multi-stage builds do this by compiling in one stage and copying artefacts into a clean final stage. Our worked guidance is in Dockerfile multi-stage builds and Docker image size reduction.

Base images deserve a policy. We pin by version, prefer a small image whose libc matches your dependencies, and decide who rebuilds images when a base is patched. An unpinned latest tag is the most common cause of builds that work on Monday and fail on Tuesday.

# syntax=docker/dockerfile:1
FROM golang:1.23 AS build
WORKDIR /src
COPY go.mod go.sum ./
RUN --mount=type=cache,target=/go/pkg/mod go mod download
COPY . .
RUN --mount=type=cache,target=/root/.cache/go-build \
    CGO_ENABLED=0 go build -o /out/app ./cmd/app

FROM gcr.io/distroless/static-debian12:nonroot
COPY --from=build /out/app /app
USER nonroot
ENTRYPOINT ["/app"]

The cache mounts keep module and build caches between runs, and the final image has no shell or package manager. Distroless images need a debugging plan (ephemeral debug containers in Kubernetes work well), so we agree that up front.

Layer order and build context

Docker caches each instruction. Copy dependency manifests first, install, then copy source, and a code change reuses the expensive layer. A .dockerignore file keeps .git, local dependencies and test data out of the build context, which speeds every build and prevents accidental leaks. See dockerignore and layer caching.

Security and supply chain

Image security is mostly discipline. We run containers as a non-root user, drop Linux capabilities the application does not need, and use a read-only root filesystem where possible. Secrets never go into ARG, ENV or COPY, because every layer is readable by anyone who can pull the image.

In CI we scan with Trivy, fail builds on severities you have agreed, and sign images with Cosign so the cluster can verify what it runs. The detail is in container security hardening and image signing with Cosign. Scanning is a snapshot: a clean image today can have a published CVE tomorrow, so we also rebuild on a schedule.

Cost and capacity

Docker itself is free to use for most teams, but images cost money in three places: registry storage, data transfer and slow pipelines. Lifecycle rules that expire untagged and old images keep storage predictable. Pull-through caches and regional replication cut cross-region transfer. Smaller images also shorten node start times, which matters when Karpenter or another autoscaler adds capacity under load.

Check Docker Desktop licensing for your organisation if developers use it. Some companies move to alternatives such as Podman or Colima for local work, which is a legitimate choice as long as images stay OCI compliant.

Day-2 operations

Operating Docker well means treating images like software with a lifecycle:

  • Rebuild base images on a schedule and roll the updates through CI, rather than waiting for a vulnerability to force it.
  • Monitor image pull failures and registry rate limits, which are a classic cause of failed deployments.
  • Keep Dockerfile linting (hadolint) in the pipeline so standards survive team changes.
  • Track image provenance so you can answer which commit built the image running in production.

Our build pipeline integration work wires these into your existing CI.

Build tool, runtime and local development

It helps to separate three roles that the word Docker covers. Docker Engine and BuildKit build images. A container runtime such as containerd runs them on servers and Kubernetes nodes. Docker Desktop or an alternative gives developers a local environment. Teams get into trouble when they assume all three must be the same product.

For local work we usually standardise on Compose files that start the application plus its databases and queues, with health checks so dependencies are ready before the app connects. Keeping those definitions close to the production configuration reduces the classic gap where something works locally and fails in a cluster. See Docker Compose for local development for how we structure it.

Common failure modes

  • Giant images from build tools left in the final stage, causing slow deploys and large scan reports.
  • Root by default, which turns a small application bug into a larger incident.
  • Mutable tags such as latest, which make rollbacks guesswork. Use immutable tags or digests.
  • PID 1 problems: shell-form CMD swallows signals, so containers ignore termination and are killed after the grace period. Use exec form.
  • Memory limits ignored by the runtime, for example JVM or Node settings that assume the whole host. We set limits and runtime flags together.
  • Compose in production without a plan for restarts, rollouts or host failure.

When Docker is not the answer

Not every workload needs a container you maintain. Small event-driven tasks fit serverless functions better. If a team of five runs three services, a managed container service such as ECS on Fargate or Cloud Run may beat Kubernetes on effort. When you do outgrow Compose, we help with moving from Compose to Kubernetes, and package the result with Helm on a cluster such as Amazon EKS. If you want a second opinion on your current images, get in touch.

Official documentation: docs.docker.com

How we work with Docker

  1. 01

    Audit existing images

    We build every Dockerfile, then record size, build time, base image age, user, exposed secrets and scan findings.

  2. 02

    Set the standards

    We agree base images, tagging, labels and a Dockerfile template per language with your engineers.

  3. 03

    Rework and measure

    We apply multi-stage builds, caching and non-root users in pull requests, with before and after numbers for each service.

  4. 04

    Wire into CI

    Lint, build, scan, sign and push become one pipeline job, with provenance recorded for every image.

  5. 05

    Operate the registry

    Retention rules, replication, pull-through caches and access policies are defined as code.

  6. 06

    Plan the runtime

    We confirm whether Kubernetes, ECS or a simpler runtime fits, and migrate workloads in small steps.

Docker compared with the alternatives

We recommend the tool that fits your team, not the one we know best. Here is when each option makes sense.

OptionChoose it when
DockerDocker packages an application and its dependencies into an immutable image that runs the same on a laptop, in CI and in production.
PodmanChoose it when you want daemonless, rootless container builds and runs, for example on hardened Linux build hosts. Images stay OCI compatible, so the Dockerfile skills carry over.
Buildah or KanikoChoose them for building images inside CI without a privileged Docker daemon, which is common on shared Kubernetes runners.
Cloud Native BuildpacksChoose them when you want images produced from source without maintaining Dockerfiles, and your languages are well supported.
Serverless functionsChoose them when a workload is small, event driven and does not justify owning an image and a runtime at all.

Tools we use alongside Docker

BuildKitDocker BuildxDocker ComposecontainerdTrivyCosignhadolintAmazon ECRHarborGitHub ActionsKubernetesHelm

Frequently asked questions

Is Docker still used with Kubernetes?
Yes for building images, less so for running them. Kubernetes removed its built-in Docker Engine integration, and nodes now use runtimes such as containerd. Images built with Docker follow the OCI standard, so they run unchanged. Your Dockerfiles, registries and Compose files all remain useful.
How do we reduce Docker image size?
Use a multi-stage build so compilers and caches stay out of the final image, start from a slim or distroless base, and copy only runtime artefacts. A well maintained .dockerignore also keeps build context small. Smaller images pull faster during deploys and autoscaling and carry fewer packages to patch.
Should containers run as root?
No. Add a dedicated user in the Dockerfile and set it with USER. A process running as root inside the container has more room to cause damage if it is compromised. Combine it with a read-only root filesystem and dropped capabilities where the application allows.
Docker Compose or Kubernetes for production?
Compose suits local development and small single-host deployments. Kubernetes or a managed service is better once you need rolling updates, self-healing across hosts, autoscaling or multiple teams sharing a platform. Many teams run Compose locally and Kubernetes in production from the same images.
How do we stop secrets ending up in images?
Never use ARG or ENV for credentials, because they persist in image history. Use BuildKit secret mounts for build time needs and inject runtime secrets from your platform at start. Scan images for leaked credentials in CI as a backstop.
Which registry should we use?
Use the registry closest to your runtime: ECR on AWS, ACR on Azure, Artifact Registry on Google Cloud. They integrate with cloud identity and scanning. Harbor suits teams that need a self-hosted or multi-cloud registry with replication.

Talk to an engineer about Docker

Book a free 30-minute call. We review your current setup and tell you honestly whether and how we can help.

Written by Saddique Khan, DevOps / SRE / Cloud Consultant Engineer. Last updated 2026-10-09.