Docker and Kubernetes: Orchestrating Your Services Without Getting Lost
Containers are the easy part. Running dozens of them reliably is where it gets hard. Here is a practical primer: multi-stage Docker builds, when you actually need Kubernetes, the core objects, health checks, and the managed alternatives.
Docker changed how we build and ship applications. Kubernetes changed how we run them at scale, and added a lot of operational surface in the process. Knowing both, and knowing when you do not need the second one, is now part of the job for any backend engineer on distributed systems.
Docker: build a small, correct image
A container packages your application with its dependencies into a portable, reproducible unit, defined by the Dockerfile. Two things matter for a production image: use a multi-stage build so the final image contains only the runtime and the built app, not the build tools, and run as a non-root user. Add a .dockerignore so you do not copy .git, node_modules or local env files into the build context.
# Stage 1: build
FROM composer:2 AS vendor
WORKDIR /app
COPY composer.json composer.lock ./
RUN composer install --no-dev --optimize-autoloader --no-scripts
# Stage 2: runtime
FROM php:8.4-fpm-alpine
RUN docker-php-ext-install pdo_mysql opcache
WORKDIR /var/www
COPY --from=vendor /app/vendor ./vendor
COPY . .
RUN php artisan config:cache && php artisan route:cache \
&& addgroup -g 1000 app && adduser -u 1000 -G app -S app \
&& chown -R app:app /var/www
USER app
EXPOSE 9000
CMD ["php-fpm"]Docker Compose: local, and small production
For local development and for a handful of services on a single VPS, Docker Compose is enough. One file describes every service, its network, its volumes and its environment, and docker compose up brings the whole stack up. Do not reach past it until a single host genuinely cannot hold your workload or you need automatic failover.
When you actually need Kubernetes
- ✓You run more than a handful of services and a single host cannot hold them.
- ✓You need automatic scaling in response to traffic, not a fixed number of containers.
- ✓You need rolling deploys with health-gated rollout and automatic rollback.
- ✓You need self-healing: a crashed container restarted, an unhealthy node drained, with no one paged.
- ✓You have the team to operate it, or you use a managed control plane (GKE, EKS, AKS).
The core objects
Learn these five first: Pod (one or more containers scheduled together), Deployment (manages a set of identical Pods and their rollout), Service (a stable network name and load balancer for those Pods), Ingress (HTTP routing from outside the cluster), and ConfigMap and Secret (configuration and credentials injected as env or files).
A minimal Deployment with health checks
The single most common mistake in a first Kubernetes setup is skipping the probes. Without a readiness probe, traffic is sent to a Pod before it is ready; without a liveness probe, a hung Pod is never restarted. Both are a few lines.
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-service
spec:
replicas: 3
selector:
matchLabels: { app: api-service }
template:
metadata:
labels: { app: api-service }
spec:
containers:
- name: api
image: my-registry/api-service:v1.2.0
ports:
- containerPort: 9000
envFrom:
- secretRef: { name: api-secrets }
readinessProbe:
httpGet: { path: /health, port: 9000 }
initialDelaySeconds: 5
livenessProbe:
httpGet: { path: /health, port: 9000 }
initialDelaySeconds: 15
resources:
requests: { cpu: 100m, memory: 256Mi }
limits: { memory: 512Mi }Config and secrets: never bake them into the image
An image should be the same in every environment. Environment-specific values come from a ConfigMap, and credentials from a Secret (ideally backed by a real secret manager, since a plain Kubernetes Secret is only base64-encoded). Inject them as environment variables or mounted files at runtime, never at build time.
Rolling deploys and rollback
A Deployment rolls out a new image by replacing Pods gradually, waiting for each new Pod to pass its readiness probe before continuing. If the new version never becomes ready, the rollout stalls instead of taking the service down, and kubectl rollout undo returns you to the previous version.
The managed alternatives
Between Docker Compose and full Kubernetes sit managed container platforms: Google Cloud Run, AWS App Runner or ECS Fargate, Fly.io. They run your container, scale it, give it HTTPS and rolling deploys, and hand you almost none of the operational burden. For most small and mid-size services they are the right answer, and you move to Kubernetes only when you outgrow them.
| Docker Compose | Managed platform | Kubernetes | |
|---|---|---|---|
| Setup effort | Minimal | Low | High |
| Ops burden | You run the host | Almost none | Significant, or a managed control plane |
| Autoscaling | No | Yes | Yes, fine-grained |
| Good for | Local, a few services on one host | Most small to mid-size production | Many services, large scale, a platform team |
FAQ
- Do I need Kubernetes?
- Probably not yet. If you run a handful of services, Docker Compose on a VPS or a managed platform such as Cloud Run covers it with far less operational cost. Kubernetes earns its complexity once you have many services, need fine-grained autoscaling and self-healing, and have the team or a managed control plane to run it.
- What is the difference between Docker and Kubernetes?
- Docker builds and runs a single container. Kubernetes orchestrates many containers across many machines: it schedules them, gives them stable network names, load-balances, scales them, restarts unhealthy ones, and rolls out new versions. Docker is the unit; Kubernetes is the system that runs thousands of units.
- Is Docker Compose enough for production?
- For a small number of services on a single host with modest traffic, yes. It is simple, well understood, and easy to reason about. You outgrow it when one host cannot hold the workload, or when you need automatic failover and traffic-based scaling that Compose does not provide.
- What is a liveness probe?
- A liveness probe is a periodic check Kubernetes runs against a container to decide whether it is still healthy. If it fails repeatedly, Kubernetes restarts the container. It catches hung processes that are running but not working. A readiness probe is separate: it decides whether the container should receive traffic yet.
- Kubernetes or a managed platform?
- Start with a managed platform (Cloud Run, App Runner, Fly). It gives you scaling, HTTPS and rolling deploys with almost no ops work. Move to Kubernetes when you need capabilities they do not offer, such as complex networking, stateful workloads, or many services sharing a cluster, and you have the team for it.
Docker is a skill everyone on a backend team needs. Kubernetes is a tool for a specific scale, and reaching for it too early trades a simple problem for a hard one. Build small correct images, run them with Compose or a managed platform, and adopt Kubernetes when the workload, not the resume, calls for it.
Need help with this topic? Microservices Migration
Discover this service →