Skip to content

.dockerignore (Node Modules/Dist)

In Stage 2: Runner of the multi-stage Docker build (FROM caddy:alpine AS runner), the instruction CMD ["caddy", "file-server", "--root", "/usr/share/caddy", "--listen", ":80"] defines the primary runtime process that keeps the container active and serves the compiled Astro portfolio to the web.


  1. Breakdown of Command Arguments

Every flag in this command configures a specific runtime behavior for Caddy:

  • "caddy": Launches the Caddy web server application.
  • "file-server": Instructs Caddy to operate specifically as a static file server rather than as a reverse proxy or dynamic application server. Its sole job is to fetch static files from disk and hand them to web browsers over HTTP.
  • "--root", "/usr/share/caddy": Defines the target directory where Caddy looks for web assets. This matches the exact destination path where Stage 1's compiled static output was transferred during the COPY --from=builder /app/dist /usr/share/caddy step.
  • "--listen", ":80": Forces Caddy to listen for incoming web requests on port 80 (standard HTTP) inside the container, directly aligning with the EXPOSE 80 declaration.

  1. Role in the Stage 2 Container Lifecycle

In the larger context of Stage 2: Runner, this command fulfils two critical operational functions:

  • Keeping the Container Alive: A Docker container stays running only as long as its foreground process (CMD) remains active. Because Astro strips away its internal development server during npm run build, the site consists purely of unexecutable HTML/CSS/JS files. Running Caddy in the foreground provides an active daemon process; without it, the container would exit immediately upon starting.
  • Zero-Node Production Environment: By running Caddy as a standalone static file server, Stage 2 serves the compiled assets directly from disk without requiring Node.js, node_modules, or build scripts in the live environment.

  1. Integration into the Homelab Reverse Proxy Architecture

Inside the broader production homelab setup, this CMD provides the internal endpoint for traffic routing:

  1. Docker Bridge Isolation: The container boots up running Caddy on port 80 and attaches to a shared frontend Docker bridge network without exposing host ports directly to the internet.
  2. Central Reverse Proxy Routing: A central host Caddy instance receives incoming traffic for the domain (e.g., http://ritikkumar.dev).
  3. Docker DNS Forwarding: Central Caddy delegates requests using internal Docker DNS (reverse_proxy astro_portfolio:80). The internal Caddy process executed by this CMD receives the forwarded traffic on port 80, grabs the static files from /usr/share/caddy, and sends the HTML response back.

Docker Layer Caching

In Stage 2: Runner of the multi-stage Docker build (FROM caddy:alpine AS runner), the instruction CMD ["caddy", "file-server", "--root", "/usr/share/caddy", "--listen", ":80"] defines the primary runtime process that keeps the container active and serves the compiled Astro portfolio to the web.


  1. Breakdown of Command Arguments

Every flag in this command configures a specific runtime behavior for Caddy:

  • "caddy": Launches the Caddy web server application.
  • "file-server": Instructs Caddy to operate specifically as a static file server rather than as a reverse proxy or dynamic application server. Its sole job is to fetch static files from disk and hand them to web browsers over HTTP.
  • "--root", "/usr/share/caddy": Defines the target directory where Caddy looks for web assets. This matches the exact destination path where Stage 1's compiled static output was transferred during the COPY --from=builder /app/dist /usr/share/caddy step.
  • "--listen", ":80": Forces Caddy to listen for incoming web requests on port 80 (standard HTTP) inside the container, directly aligning with the EXPOSE 80 declaration.

  1. Role in the Stage 2 Container Lifecycle

In the larger context of Stage 2: Runner, this command fulfils two critical operational functions:

  • Keeping the Container Alive: A Docker container stays running only as long as its foreground process (CMD) remains active. Because Astro strips away its internal development server during npm run build, the site consists purely of unexecutable HTML/CSS/JS files. Running Caddy in the foreground provides an active daemon process; without it, the container would exit immediately upon starting.
  • Zero-Node Production Environment: By running Caddy as a standalone static file server, Stage 2 serves the compiled assets directly from disk without requiring Node.js, node_modules, or build scripts in the live environment.

  1. Integration into the Homelab Reverse Proxy Architecture

Inside the broader production homelab setup, this CMD provides the internal endpoint for traffic routing:

  1. Docker Bridge Isolation: The container boots up running Caddy on port 80 and attaches to a shared frontend Docker bridge network without exposing host ports directly to the internet.
  2. Central Reverse Proxy Routing: A central host Caddy instance receives incoming traffic for the domain (e.g., http://ritikkumar.dev).
  3. Docker DNS Forwarding: Central Caddy delegates requests using internal Docker DNS (reverse_proxy astro_portfolio:80). The internal Caddy process executed by this CMD receives the forwarded traffic on port 80, grabs the static files from /usr/share/caddy, and sends the HTML response back.

Lightweight Image footprint

In the larger context of build and deployment optimizations, achieving a lightweight image footprint is the primary goal of the multi-stage Docker build architecture. By separating the build environment from the production runtime, the final production container is stripped down to an ultra-lean footprint under 20MB total.


  1. The "Throwaway Builder" Pattern

A standard single-stage build retains everything used during compilation—Node.js, package managers (npm/pnpm), build tools, source code, and heavy dependency folders (node_modules).

The project solves this using a multi-stage build:

  • Stage 1 (builder): Uses node:20-alpine to install dependencies and compile the Astro components into plain static HTML, CSS, and JavaScript files inside /app/dist.
  • Stage 2 (runner): Starts fresh from a minimal web server base image (caddy:alpine or nginx:alpine).
  • Selective Transfer: The directive COPY --from=builder /app/dist ... copies only the compiled static bundle into the web server's root serving directory.

Once Stage 1 completes, the entire Node.js environment, build tooling, and node_modules directory are thrown away.


  1. Alpine Linux Base Image Selection

The footprint is further optimized by choosing Alpine Linux variants for both stages (node:20-alpine, caddy:alpine, and nginx:alpine):

  • Alpine distributions eliminate bloated operating system packages, providing an extremely lightweight base OS footprint.
  • Clearing default placeholder assets (RUN rm -rf /usr/share/nginx/html/*) ensures no unnecessary files remain in the final web root before copying compiled assets.

  1. Optimization Benefits in the DevOps Pipeline

Achieving a sub-20MB footprint delivers three major architectural advantages across local development, registry storage, and server deployment:

  • Minimal Storage & Network Overhead: Pushing images to GitHub Container Registry (GHCR) and pulling them down to homelab servers (docker compose pull) takes seconds over the network rather than minutes.
  • Hardened Security & Reduced Attack Surface: Removing the Node.js runtime, compilers, shells, and dev dependencies eliminates Node-related runtime vulnerabilities and unneeded CLI tools from the live production container.
  • Resource Efficiency: Serving pre-compiled static assets from an Alpine Caddy/Nginx container requires negligible RAM and CPU on the homelab server compared to running a heavy Node.js or SSR application process.