.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.
- 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/caddystep. - "--listen", ":80": Forces Caddy to listen for incoming web requests on port 80 (standard HTTP) inside the container, directly aligning with the
EXPOSE 80declaration.
- 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 duringnpm 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.
- Integration into the Homelab Reverse Proxy Architecture
Inside the broader production homelab setup, this CMD provides the internal endpoint for traffic routing:
- Docker Bridge Isolation: The container boots up running Caddy on port 80 and attaches to a shared
frontendDocker bridge network without exposing host ports directly to the internet. - Central Reverse Proxy Routing: A central host Caddy instance receives incoming traffic for the domain (e.g.,
http://ritikkumar.dev). - Docker DNS Forwarding: Central Caddy delegates requests using internal Docker DNS (
reverse_proxy astro_portfolio:80). The internal Caddy process executed by thisCMDreceives 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.
- 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/caddystep. - "--listen", ":80": Forces Caddy to listen for incoming web requests on port 80 (standard HTTP) inside the container, directly aligning with the
EXPOSE 80declaration.
- 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 duringnpm 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.
- Integration into the Homelab Reverse Proxy Architecture
Inside the broader production homelab setup, this CMD provides the internal endpoint for traffic routing:
- Docker Bridge Isolation: The container boots up running Caddy on port 80 and attaches to a shared
frontendDocker bridge network without exposing host ports directly to the internet. - Central Reverse Proxy Routing: A central host Caddy instance receives incoming traffic for the domain (e.g.,
http://ritikkumar.dev). - Docker DNS Forwarding: Central Caddy delegates requests using internal Docker DNS (
reverse_proxy astro_portfolio:80). The internal Caddy process executed by thisCMDreceives 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.
- 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-alpineto 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:alpineornginx: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.
- 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.
- 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.