All notes
DevOpsDockerDeploymentInfrastructure

Why We Deploy Containers Instead of Copying Code

3 min read

Many teams begin their production deployments like this:

  • SSH into the server
  • Pull the latest code
  • Run commands
  • Restart the app

This approach works — until environments become inconsistent.

The Problem

Over time, every server becomes a snowflake:

  • Different dependency versions installed manually
  • One-off fixes applied directly on the machine
  • Rollbacks that require remembering what changed and when

This leads to deployment instability and operational risk. Each server becomes special, fragile, and hard to replace. The issue is not deployment speed — it is lack of reproducibility.

The Solution: Containers

Instead of deploying source code, we deploy immutable container images.

Each image contains:

  • Runtime (Ruby, Node, etc.)
  • Dependencies pinned at exact versions
  • Application code
  • Precompiled assets

The image is built once and remains identical across every environment — development, staging, production.

# Example: Minimal production Dockerfile
FROM ruby:3.3-slim

WORKDIR /app

COPY Gemfile Gemfile.lock ./
RUN bundle install --without development test

COPY . .
RUN bundle exec rails assets:precompile

CMD ["bundle", "exec", "puma", "-C", "config/puma.rb"]

What Changes

The server's job becomes simple:

  • Run Docker
  • Run Nginx as a reverse proxy
  • Start containers from the registry

The server does not:

  • Install dependencies
  • Pull source code
  • Modify the runtime environment

Benefits

| Before (code deploy) | After (container deploy) | |---|---| | Environment drift | Identical environments | | Hard rollbacks | Pull previous image tag | | Snowflake servers | Replaceable infrastructure | | "Works on my machine" | Same image everywhere |

  • Predictable deployments — what you test is what runs in production
  • Easy rollback — revert to a previous image in seconds
  • No configuration drift — environments stay consistent
  • Replaceable servers — infrastructure becomes disposable

Deployment Flow

# 1. Build the image
docker build -t myapp:v1.2.3 .

# 2. Push to registry
docker push registry.example.com/myapp:v1.2.3

# 3. On the server — pull and run
docker pull registry.example.com/myapp:v1.2.3
docker stop myapp && docker rm myapp
docker run -d --name myapp \
  -p 3000:3000 \
  --env-file .env.production \
  registry.example.com/myapp:v1.2.3

Takeaway

Servers should run containers, not code.

Immutability reduces operational risk. When every deployment is a known, tested, versioned artifact — not a sequence of commands run on a live server — reliability goes up and surprises go down. Infrastructure should be replaceable, not maintained.