From Laptop to Production: Docker and Terraform for Full-Stack Apps 🐳
A practical guide to deploying full-stack apps with Docker and Terraform, from local development to a reproducible production pipeline.
"It works on my machine" is still the most expensive sentence in software. Here's the deployment pattern I reach for on modern full-stack projects: containerize with Docker, provision with Terraform, and treat both as code that lives in the repo, not tribal knowledge.
Why Containerize First
A Dockerfile forces you to write down every dependency your app actually needs — the runtime version, system libraries, build steps — instead of relying on whatever happens to be installed on a developer's laptop or a CI runner.
FROM node:20-alpine AS builder
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN corepack enable && pnpm install --frozen-lockfile
COPY . .
RUN pnpm build
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY --from=builder /app/.next ./.next
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./package.json
EXPOSE 3000
CMD ["pnpm", "start"]
Multi-stage builds like this keep the final image lean — build tools and dev dependencies never ship to production, only the compiled output and runtime dependencies do.
Why Infrastructure as Code
Manually clicking through a cloud console to create a database, a load balancer, and networking rules is fine for a demo. It falls apart the moment you need a second environment, a disaster recovery plan, or a second engineer who wasn't in the room when it was set up.
Terraform describes infrastructure declaratively:
resource "aws_ecs_service" "app" {
name = "portfolio-app"
cluster = aws_ecs_cluster.main.id
task_definition = aws_ecs_task_definition.app.arn
desired_count = 2
load_balancer {
target_group_arn = aws_lb_target_group.app.arn
container_name = "app"
container_port = 3000
}
}
Run terraform plan and you see exactly what will change before it happens. Run terraform apply and that state is reproducible, reviewable in a pull request, and destroyable in one command if an environment needs to be torn down.
The Full Pipeline
- Build: Docker image built and tagged with the git SHA, not
latest— every deploy is traceable to an exact commit. - Push: Image pushed to a container registry (ECR, GCR, or a private registry).
- Provision: Terraform ensures the target infrastructure (compute, networking, database, secrets) matches the declared state.
- Deploy: The orchestrator (ECS, Kubernetes, or a PaaS) pulls the new image and performs a rolling update, so there's no downtime window.
- Rollback: Because both the image tag and the infrastructure state are versioned, rolling back is redeploying the previous known-good SHA, not a manual recovery scramble.
Lessons From Real Deployments
Keep secrets out of the image. Environment variables and secrets belong in a secrets manager or the orchestrator's config, injected at runtime — never baked into a Docker layer.
Pin your base images. node:20-alpine today is not the same bytes as node:20-alpine in three months. Pin to a digest for anything that needs to be exactly reproducible.
Terraform state is not optional to manage carefully. Remote state with locking (S3 + DynamoDB, or Terraform Cloud) prevents two engineers from applying conflicting changes at the same time.
This is the same Docker-plus-Terraform pattern behind the multi-cloud ML deployment platform we built to provision and monitor model infrastructure across AWS, GCP, and Azure from one control plane.
Takeaway
Docker answers "what does this app need to run?" Terraform answers "what does the environment around it need to look like?" Together, they turn a deployment from a manual checklist into something you can review, version, and roll back like any other code change.
Shipping a full-stack app? If you can't tear the whole environment down and rebuild it from the repo alone, it isn't fully infrastructure as code yet.
Continue Reading
Fine-Tuning vs RAG: A Practical Guide to Grounding LLMs
RAG vs fine-tuning: a practical breakdown of when to use each to ground LLMs in real data, and how production AI/ML systems combine both.
Read article🎙️Building Production-Ready AI Voice Agents with LLMs
How we engineered a production AI voice agent with real-time speech-to-text, LLM inference, and voice synthesis — conversational AI that feels human, not robotic.
Read article