Back to Articles
DevOpsDockerTerraformFull Stack

From Laptop to Production: Docker and Terraform for Full-Stack Apps 🐳

By Hamza Ayoub3 min read

A practical guide to deploying full-stack apps with Docker and Terraform, from local development to a reproducible production pipeline.

Share:

"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

  1. Build: Docker image built and tagged with the git SHA, not latest — every deploy is traceable to an exact commit.
  2. Push: Image pushed to a container registry (ECR, GCR, or a private registry).
  3. Provision: Terraform ensures the target infrastructure (compute, networking, database, secrets) matches the declared state.
  4. Deploy: The orchestrator (ECS, Kubernetes, or a PaaS) pulls the new image and performs a rolling update, so there's no downtime window.
  5. 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.

LET'S WORK TOGETHER

Have a system that needs to ship like this?

Get in touch