How Docker builds container images and Kubernetes runs them in production.
At a high level, Docker packages an application into a container image, while Kubernetes runs and coordinates containers across a cluster. They are often discussed together, but they solve different problems and neither one requires the other.
This guide takes the same application from a Dockerfile to Docker Compose and then to a Kubernetes Deployment - explaining what Kubernetes adds around the container, including scheduling, service discovery, scaling, rollouts, configuration, and the infrastructure required to operate a cluster.
Docker gives developers a consistent way to package a process with its runtime dependencies. A Dockerfile describes how to build the image, a registry stores and distributes it, and Docker Engine can create an isolated container from it.
A multi-stage build keeps compilers and development dependencies out of the final image. For a small Node application, that can look like this:
FROM node:22-bookworm-slim AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:22-bookworm-slim
ENV NODE_ENV=production
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY --from=build /app/dist ./dist
USER node
CMD ["node", "dist/server.js"]
The build produces an immutable image; starting it creates a container. Configuration, secrets, network access, and persistent data arrive at runtime and stay out of the image itself.
An API, database, and cache can share networking and volumes on one development machine through Docker Compose, but the same file does not turn them into a highly available multi-node production platform.
Kubernetes provides an API for declaring the desired state of workloads across a cluster. A Deployment says which image should run and how many replicas should exist; controllers continually work to make the cluster match that declaration.
apiVersion: apps/v1
kind: Deployment
metadata:
name: orders-api
spec:
replicas: 3
selector:
matchLabels:
app: orders-api
template:
metadata:
labels:
app: orders-api
spec:
containers:
- name: api
image: registry.example.com/orders@sha256:abc123...
ports:
- containerPort: 8080
readinessProbe:
httpGet:
path: /ready
port: 8080
resources:
requests:
cpu: 250m
memory: 256Mi
Replace the placeholder image reference with a real registry image and digest; the application must listen on port 8080 and implement /ready. The Dockerfile also assumes a Node project with a lockfile, a build script, and a dist/server.js output.
Kubernetes creates Pods from the image and replaces them when they fail or during a rollout. A Service gives those changing Pods a stable network address; Ingress or a gateway can route external traffic; ConfigMaps and Secrets deliver runtime values; volumes connect workloads that require persistent storage.
The example only covers the workload layer because a production cluster still needs nodes or managed compute, networking, DNS, ingress, certificate handling, identity, policy, observability, upgrades, and a delivery process. Managed Kubernetes removes parts of the control-plane work, but the platform still needs an owner.
Kubernetes runs containers through its Container Runtime Interface. Current clusters commonly use runtimes such as containerd or CRI-O, while the built-in dockershim adapter for Docker Engine was removed in Kubernetes 1.24.
Docker images did not become incompatible when dockershim disappeared. They follow Open Container Initiative standards, so Kubernetes-compatible runtimes can continue pulling and running them:
Dockerfile → docker build → OCI image → registry → Kubernetes runtime
What disappeared was Docker Engine's special position inside the Kubernetes node, not Docker as an image-building workflow. Commands such as docker build, Docker Desktop, and Compose can remain part of local development even when production uses Kubernetes.
A Compose file and a set of Kubernetes manifests may describe similar processes, ports, volumes, and environment variables, but their operating assumptions are different.
Moving from Compose to Kubernetes is not a mechanical file conversion. Production concerns that were implicit on one host need explicit decisions about availability, scheduling, storage, exposure, policy, and ownership. A generated manifest may provide a starting point, but it cannot make those architectural choices for the team.
Kubernetes makes sense when many workloads need consistent deployment and policy, a platform team can operate the cluster and its add-ons, or the organization depends on Kubernetes-specific controllers and tooling. In those cases its API can become the foundation for a shared platform, with scheduling controls for specialized hardware, mixed workload shapes, and placement requirements.
It is harder to justify when a small team operates a handful of conventional APIs and workers. ECS Fargate, Cloud Run, or another managed platform may supply the deployment, scaling, and health management the application needs without turning cluster operation into a second product. Docker remains useful in that architecture even though Kubernetes never enters it.
Docker can remain the packaging workflow whether production uses Kubernetes, ECS, Cloud Run, or something else. Kubernetes only belongs in the decision once the team needs its cluster API and operating model.
If the application is a set of APIs and workers connected to managed databases, queues, buckets, caches, and secrets, Encore offers a higher-level deployment path. You keep the application framework and declare the supported infrastructure alongside the code; Encore provisions it in your cloud account and manages the environment-specific wiring.
Encore can select managed compute such as ECS Fargate on AWS, connect it to the declared resources, and generate the deployment configuration, IAM, and networking around the application, without requiring a separate Docker and Kubernetes workflow for each service.
import { SQLDatabase } from "encore.dev/storage/sqldb";
const db = new SQLDatabase("todo", {
migrations: "./migrations",
});Managed PostgreSQL with migrations, pooling and IAM auth
The infrastructure follows the application's resource declarations rather than a collection of container manifests.
Encore handles deployment and application infrastructure, while if you need direct control over scheduling and cluster configuration, Kubernetes gives you that flexibility.
Run in your terminal to get started locally.
Docker provides tools to build container images and run containers. Kubernetes coordinates containers across a cluster, maintaining workloads, networking, configuration, storage, and deployments.
Yes. Images built with Docker use the OCI image format and can run on Kubernetes through a compatible container runtime such as containerd or CRI-O.
Kubernetes no longer includes dockershim, the adapter that let kubelet talk to Docker Engine. It still runs images built with Docker because those images follow open container standards.
No. Docker can be used without Kubernetes for development, CI, single-host deployments, or with managed container services such as ECS and Cloud Run. Kubernetes is useful when its cluster APIs and ecosystem solve an operational need.
Encore can deploy backend services to managed compute such as ECS Fargate on AWS or Cloud Run on GCP. Databases, queues, and storage are declared in application code and provisioned as part of the deployment.
Deploy to your cloud on AWS and Google Cloud
without managing infrastructure
Encore lets developers and agents define application infrastructure directly in code, then automatically provisions it from local development to production in your AWS or GCP account.