Deploying modern web applications requires robust, automated cloud infrastructure — and the gap between "it works on my machine" and "it reliably serves production traffic" is where most of the real engineering happens. Here's what that pipeline actually looks like in practice, from containerizing an app to deciding when serverless beats Kubernetes outright.

Cloud Infrastructure Data Servers and DevOps Setup

Docker: multi-stage builds are not optional

The single biggest mistake I still see in Dockerfiles is shipping the entire build toolchain — compilers, dev dependencies, source maps, test fixtures — inside the production image. A multi-stage build fixes this by using one stage to install dependencies and build the application, then copying only the compiled output into a lean final stage based on a minimal runtime image.

The difference isn't cosmetic. A bloated image means slower deploys, slower cold starts if you're running on anything autoscaling, and a meaningfully larger attack surface since every unnecessary package is a potential CVE you now have to track. A well-built multi-stage Node.js image can easily go from 1.2GB down to under 150MB just by separating the build stage from the runtime stage and using an -alpine or distroless base for the latter.

Kubernetes: the parts that actually matter day to day

Kubernetes has a reputation for being needlessly complex, and honestly, a lot of that reputation is earned — but the core concepts you need for daily operation are smaller than the ecosystem around them suggests:

  • Pods are the smallest deployable unit — usually one container, sometimes a tightly coupled pair (like an app container plus a logging sidecar).
  • Deployments manage a set of identical pod replicas and handle rolling updates, so you can ship a new version without downtime and roll back instantly if something's wrong.
  • Services give a stable network identity to a shifting set of pods, so nothing else in your cluster needs to care that pods get replaced constantly.
  • Ingress handles routing external traffic into the cluster, typically where your TLS termination and domain-based routing rules live.

Where teams get into trouble is skipping resource requests and limits on their pods. Without them, one misbehaving service can starve every other workload on the same node, and Kubernetes has no way to know it should intervene. Setting sane CPU and memory requests isn't glamorous work, but it's the difference between one bad deploy taking down your whole cluster and it staying contained to itself.

When serverless actually wins

Serverless (Lambda, Cloud Run, Cloud Functions) gets pitched as the default modern choice, but it's genuinely the better fit only for a specific shape of workload: spiky or unpredictable traffic, short-lived request/response cycles, and logic that doesn't need to hold persistent in-memory state between requests.

Where it stops being the right call: anything with long-running connections (WebSockets, persistent database connection pools you want to reuse across requests), workloads with tight cold-start latency requirements, or steady, predictable high-volume traffic where the per-invocation pricing model of serverless actually costs more than a right-sized set of always-on containers. I've seen teams migrate a steady-traffic API to Lambda expecting savings and end up with a bill that's two or three times what a couple of small Kubernetes nodes would have cost — the marketing narrative around serverless being "cheaper by default" only holds for the traffic patterns it's actually designed for.

CI/CD: keep the pipeline boring

A good deployment pipeline should be almost aggressively unremarkable: push to a branch, automated tests run, a container image builds and gets pushed to a registry, and a deploy step rolls it out — with an automatic rollback if health checks fail post-deploy. The temptation to add clever custom scripting at every stage is real, but every custom step is something a new team member has to learn and something that can silently break when a dependency updates. GitHub Actions, GitLab CI, or a managed option like Cloud Build are all more than sufficient for the vast majority of teams; the pipeline tooling itself is rarely the bottleneck.

Observability: you can't fix what you can't see

Logs, metrics, and traces each answer a different question, and teams that only have logs are flying half-blind. Metrics (request rate, error rate, latency percentiles — the classic "RED" method) tell you that something's wrong and roughly how bad. Traces tell you where in a request's path across multiple services the time actually went, which is the piece that turns a two-hour debugging session into a five-minute one once you have it wired up. Setting up basic OpenTelemetry instrumentation early, before you actually need it in a crisis, pays for itself the first time a production incident happens at 2am and you're not grepping through raw logs trying to reconstruct what happened.

The real lesson

None of this infrastructure is interesting on its own — the goal is always to make deploys boring, incidents self-diagnosing, and the whole system quiet enough that nobody has to think about it most days. The teams that get this right treat infrastructure work as a foundation to build on top of, not a badge of technical sophistication to show off.

Cost optimization that doesn't require a platform team

Cloud bills creep up gradually, and most of the growth comes from a small number of predictable sources rather than one dramatic mistake. Idle resources — dev/staging environments left running 24/7, orphaned load balancers from a deleted service, oversized instances chosen "to be safe" and never revisited — typically account for a bigger share of a growing bill than actual production traffic growth. A monthly fifteen-minute review of your cloud provider's cost breakdown by service catches most of this before it compounds into a real problem, and it's a habit worth building before your infrastructure gets complex enough that nobody has a full picture of what's actually running.

Right-sizing is the other consistent win: most teams provision resources based on a rough guess when a service launches, then never revisit it once things are stable. Actual CPU and memory utilization metrics, collected over a couple of weeks of real traffic, almost always reveal that some services are over-provisioned by a significant margin — and downsizing those, verified against your actual utilization data rather than gut feeling, is one of the few cost optimizations that carries essentially zero risk.

Common pitfalls that show up almost every time

A few mistakes show up often enough across teams that they're worth calling out directly. Secrets committed to version control, even briefly and even in a private repo, should be treated as compromised and rotated immediately — git history doesn't forget, and a "private repo" is not the security boundary people assume it is once more than a couple of people have access. Missing health checks on deployments mean Kubernetes or your load balancer can keep routing traffic to a pod that's actually broken, because nothing told the orchestrator it was unhealthy. And skipping a staging environment that actually mirrors production configuration — not just a scaled-down version, but the same environment variables, the same external service connections — means your "it worked in staging" confidence doesn't transfer to what happens when the real deploy goes out, which is exactly the gap that turns a routine deploy into an incident.