Showing posts with label Docker. Show all posts
Showing posts with label Docker. Show all posts

Thursday, February 12, 2026

Docker - Exclude Dev Tools (Docker Optimization)

Do not include development-only tools and packages in the final production container image.

You only keep what is needed to run the app — not what is needed to build or test it.

This reduces image size, vulnerabilities, and startup time, which is very important in AWS environments.

“Removing dev dependencies ensures the production Docker image only contains runtime components, resulting in smaller image size, faster AWS deployments, reduced vulnerabilities, and lower CI/CD and ECR costs.”

How to Achieve: Multi stage docker build 
Create docker file in such a way that makes multi stage build (build then run) 





What Are Dev Dependencies?

These are packages/tools used during:

  • Local development
  • Unit testing
  • Debugging
  • Building/compiling
  • Linting/formatting

But not needed at runtime.

Examples:

TechnologyDev Dependencies
.NETSDK, test frameworks, analyzers
Node.jseslint, nodemon, jest
Pythonpytest, black
Javamaven, gradle

Why Remove Them?
1. Smaller Image

Dev tools are heavy.

Example:

  • With SDK + test tools → 1.2 GB
  • Runtime only → 200 MB

Smaller image = faster pull from ECR.


2. Faster Startup on AWS

Impacts:

  • ECS task start
  • EKS pod start
  • Lambda container cold start


3. Better Security

Dev packages increase CVEs.

Removing them:

  • Reduces attack surface
  • Better ECR / Trivy scan results

4. Lower AWS Cost
  • Less ECR storage
  • Less data transfer
  • Faster CI/CD = lower CodeBuild minutes

How to Remove Dev Dependencies

.NET Example
Wrong (Includes SDK)

FROM mcr.microsoft.com/dotnet/sdk:8.0
WORKDIR /app
COPY . .
RUN dotnet publish -c Release
ENTRYPOINT ["dotnet", "MyApp.dll"]

SDK stays in final image → huge size.

Correct (Multi-Stage)

# Build Stage
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY . .
RUN dotnet publish -c Release -o /out

# Runtime Stage
FROM mcr.microsoft.com/dotnet/aspnet:8.0
WORKDIR /app
COPY --from=build /out .
ENTRYPOINT ["dotnet", "MyApp.dll"]

Now:

  • SDK removed
  • Only runtime DLLs remain


Node.js Example
RUN npm install --production

or

RUN npm ci --omit=dev

AWS Impact Example

Without removing dev deps:

  • Image size: 600 MB
  • ECS startup: 25 sec
  • Vulnerabilities: 120

With removal:

  • Image size: 150 MB
  • ECS startup: 6–8 sec
  • Vulnerabilities: 30


Simple Analogy

It’s like shipping a car:

  • Dev deps = factory tools, welding machines
  • Runtime deps = engine, wheels, fuel

Customer only needs the car, not the factory.

Docker - Layer Caching

Layer Caching in Docker means Docker reuses previously built image layers instead of rebuilding everything from scratch.

In AWS + .NET context, this is very important for CI/CD speed, cost, and developer productivity.

“Docker layer caching improves AWS CI/CD performance by reusing unchanged build steps. In .NET, copying the .csproj and running dotnet restore before copying source code prevents unnecessary dependency restores, reducing build time, ECR push size, and deployment latency.”




First Understand Docker Layers

Every Dockerfile instruction creates a layer:

FROM ...
WORKDIR ...
COPY ...
RUN ...

Docker stores each layer as a snapshot.

If nothing changes in that step → Docker reuses the cached layer.


Why It Matters on AWS

Layer caching helps in:

AWS ServiceBenefit
CodeBuildFaster builds
ECRSmaller push size
ECS/EKSFaster deployments
Lambda ContainersReduced cold start
CI/CD PipelinesMinutes saved per build

Example Without Layer Optimization (.NET)
FROM mcr.microsoft.com/dotnet/sdk:8.0
WORKDIR /app
COPY . .
RUN dotnet restore
RUN dotnet publish -c Release -o out

Problem:

  • Any small code change invalidates COPY . .
  • Docker reruns restore + publish
  • Slow builds (2–5 minutes)


Optimized Version Using Layer Caching
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /app

# Copy only project files first
COPY *.csproj .
RUN dotnet restore

# Then copy rest of source
COPY . .
RUN dotnet publish -c Release -o out
Why This Works

  • .csproj changes rarely
  • Source code changes frequently

Docker cache logic:

StepChanges Often?Cache Used?
COPY *.csprojRareYes
RUN dotnet restoreRareYes
COPY . .OftenNo
RUN publishOftenNo

So NuGet restore is skipped most builds, saving time.


AWS CI/CD Impact Example

Without caching:

  • Build time = 6 minutes
  • Every commit full rebuild

With caching:

  • Build time = 1.5–2 minutes
  • Only changed layers rebuilt

In CodeBuild or GitHub Actions, this saves hours per week.


ECR Push Optimization

Docker only pushes changed layers.

If only app code changed:

  • Push size maybe 20–30 MB
  • Instead of 300–500 MB

Less network → faster deploy → lower cost.


Real-World Analogy

Think of it like Excel recalculation:

  • Change one cell → not entire sheet recalculated
  • Docker only rebuilds changed steps


Best Practices for Layer Caching (.NET + AWS)

1. Order Matters

Stable steps first, changing steps later.

2. Separate Restore
COPY *.csproj .
RUN dotnet restore
3. Use .dockerignore

Exclude:

  • bin/
  • obj/
  • .git/
  • logs/

Reduces invalid cache triggers.

4. Multi-Stage Build

Keeps final image small and cache efficient.

AWS - Linux Alpine images

Using Alpine Linux as the operating system layer of your Docker image instead of a full Linux distro like Ubuntu or Debian.

It is not AWS-specific — but it is very popular on AWS because of performance and cost benefits.

“Alpine base images on AWS reduce container size, improve startup time, lower ECR costs, and reduce vulnerabilities, but require compatibility testing due to musl vs glibc differences.”


What is Alpine Linux?

Alpine Linux is a very small, security-focused Linux distribution.

Typical sizes:

Base ImageApprox Size
Ubuntu70–120 MB
Debian60–100 MB
Alpine5–15 MB

So your container becomes much smaller.


Example in Dockerfile (.NET)

Normal:

FROM mcr.microsoft.com/dotnet/aspnet:8.0

Alpine:

FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine


Sample Alpine Docker file


FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine

WORKDIR /app

COPY --from=build /app/out .

ENTRYPOINT ["dotnet", "MyApp.dll"]


Difference:

  • Same .NET runtime
  • Smaller OS layer


Why It Matters on AWS

1. Faster Image Pull (ECR → ECS/EKS/Lambda)

Smaller image = faster download.

Example:

  • Ubuntu image: 400 MB → 15–20 sec pull
  • Alpine image: 90 MB → 3–5 sec pull

This directly impacts:

  • Lambda cold start
  • ECS task startup
  • Auto-scaling speed


2. Lower Storage Cost (ECR)

AWS ECR charges for storage.

Smaller image:

  • Less GB stored
  • Lower monthly cost


3. Better Security

Alpine includes:

  • Fewer packages
  • Smaller attack surface
  • Fewer CVEs

Security scans (ECR / Trivy) usually show less vulnerabilities.


4. Faster CI/CD Pipelines

In CodeBuild / GitHub Actions:

  • Faster build
  • Faster push/pull
  • Less network usage


Where You Use It on AWS
AWS ServiceBenefit
ECS FargateFaster container start
EKS (Kubernetes)Faster pod scheduling
Lambda ContainerReduced cold start
CodeBuildFaster pipelines
ECRLower storage cost

Important Caveat (Very Important)

Alpine uses musl libc instead of glibc.

Some libraries or native dependencies may fail, especially:

  • Image processing libs
  • Oracle drivers
  • Some older .NET native packages
  • Python scientific libs

If your app depends on native binaries, test carefully.


When NOT to Use Alpine

Avoid Alpine if:

  • You need heavy native libraries
  • You see runtime crashes related to libc
  • Vendor software requires glibc
  • You need full debugging tools

In such cases use:

  • -slim images
  • Debian slim
  • Ubuntu minimal



Tuesday, February 10, 2026

AWS - Multi-Stage Docker Build

Multi stage sample docker file

# Build stage

FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build

WORKDIR /app

COPY . .

RUN dotnet publish -c Release -o out

# Runtime stage

FROM mcr.microsoft.com/dotnet/aspnet:8.0

WORKDIR /app

COPY --from=build /app/out .

ENTRYPOINT ["dotnet", "MyApp.dll"]


This Dockerfile uses Multi-Stage Build:

  • Stage 1 → Build the app
  • Stage 2 → Run the app

The RUN command executes during image build time, not when the container starts.




Stage 1 – Build Stage

FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
  • Pulls Microsoft .NET SDK image
  • SDK contains compiler, NuGet, build tools
  • Named this stage build


WORKDIR /app

  • Sets working directory inside container to /app
  • All next commands run from here


COPY . .

  • Copies your entire project folder from local machine → container /app
  • Includes .csproj, source code, etc.


RUN dotnet publish -c Release -o out

This is the key line 

RUN executes a command while building the image.

Think of it as:

“Open a temporary container, run this command, save the result as a new image layer.”

What happens internally

  1. Docker creates a temporary container from sdk:8.0
  2. Runs:
    • dotnet publish -c Release -o out
  3. .NET does:

    • Restore NuGet packages
    • Compile code
    • Optimize for Release
    • Output DLLs + dependencies into /app/out
  4. Docker commits the result as a new image layer
  5. Temporary container is deleted

So RUN is build-time execution, not runtime.

Result now inside image:

/app/out/MyApp.dll
/app/out/*.json
/app/out/*.deps.json


Stage 2 – Runtime Stage

FROM mcr.microsoft.com/dotnet/aspnet:8.0
  • Starts a fresh lightweight image
  • No SDK, only runtime
  • Much smaller


WORKDIR /app

Sets working directory again.


COPY --from=build /app/out .
  • Copies only compiled output from build stage
  • Not source code
  • Not SDK
  • Only /app/out/app

This is why image becomes small.


ENTRYPOINT ["dotnet", "MyApp.dll"]

This runs when container starts, not during build.

Difference:

InstructionWhen it runs
RUNDuring docker build
ENTRYPOINTDuring docker run

Lifecycle Summary
When you run:

docker build -t myapp .

Docker executes:

  1. Pull SDK image
  2. Copy code
  3. RUN dotnet publish → compile app
  4. Start new runtime image
  5. Copy compiled files
  6. Create final image


When you run:

docker run myapp

Only this runs:

dotnet MyApp.dll

No compiling happens here.


Simple Analogy

  • RUN = “Bake the cake in the kitchen”
  • ENTRYPOINT = “Serve the cake to the customer”

RUN prepares the app.
ENTRYPOINT executes the app.


Why This Is Efficient

Without multi-stage:

  • Image ~1–2 GB
  • Includes compiler

With multi-stage:

  • Image ~150–250 MB
  • Only runtime files
  • Faster deploys
  • More secure

So RUN = build-time command that produces artifacts saved into the image layer.

CI/CD - Safe DB Changes/Migrations

Safe DB Migrations means updating your database schema without breaking the running application and without downtime . In real systems (A...