DEVOPS · INFRASTRUCTURE
Portfolio Infrastructure - Self-Hosted CI/CD
The deployment platform behind this portfolio: an Astro application built through GitHub Actions, published as a Docker image to GHCR and automatically deployed to my TrueNAS homelab.
The portfolio is part of the portfolio.
Instead of deploying this site through a managed static-hosting platform, I chose to run the complete production environment from my own homelab.
The website is developed locally, version-controlled through GitHub, built automatically into a container image, stored in GitHub Container Registry and deployed onto my TrueNAS server.
Public traffic reaches the application through my domain and Nginx Proxy Manager, which handles HTTPS and forwards the request to the running portfolio container.
From source code to my home server.
The deployment process is designed so that normal website changes happen through Git rather than by editing files directly on the production server.
Source controlled
Production changes originate from the Git repository rather than manual modifications on the server.
Repeatable builds
The same Dockerfile is used each time the application is built, reducing differences between deployments.
Container registry
GitHub Container Registry acts as the handoff point between the CI environment and my self-hosted infrastructure.
Automated deployment
TrueNAS periodically checks for the current production image and recreates the container when a new version is available.
A containerized Astro application.
Astro is used for the main website because most portfolio content can be generated as static HTML. Interactive elements, such as the homelab architecture explorer, use React where browser-side state is actually needed.
The production build is packaged into a Docker image so the hosting environment only needs to know how to run the container.
Static-first site structure, reusable layouts and project components.
Used selectively for components such as the interactive homelab topology.
Provides a reproducible artifact that can be built in CI and executed on TrueNAS.
Current release plus immutable builds.
Container images are published with both a production tag and a commit-specific tag.
ghcr.io/.../portfolio:latestUsed by the normal deployment so the server can follow the current production release.
ghcr.io/.../portfolio:sha-<commit>Identifies a specific build and provides a straightforward rollback target when necessary.
Running production from TrueNAS.
The production container runs alongside the other services in my homelab. Docker Compose defines how the application runs, while Dockge provides a convenient interface for managing the stack.
How a visitor reaches the site.
DNS directs the portfolio hostname toward my public connection. Incoming HTTPS traffic is forwarded to Nginx Proxy Manager, which terminates TLS and proxies the request to the portfolio container.
Deployment is not finished when the container starts.
The portfolio container includes a Docker health check that verifies the web server can actually respond from inside the running container.
This distinguishes a container that merely has a running process from one that is able to serve the application.
Application endpoint responds successfully.
Keep the public surface small.
Self-hosting a public website also means owning the security boundary. The portfolio is therefore treated differently from internal administrative services in the homelab.
Reverse proxy
Public web traffic reaches the application through a dedicated reverse-proxy layer instead of exposing management interfaces directly.
HTTPS
TLS is terminated at the reverse proxy and HTTP requests can be redirected to encrypted connections.
Management separation
Administrative services are kept separate from the public portfolio and can remain LAN- or VPN-oriented.
Container boundary
The website runs as a dedicated container rather than directly modifying the underlying TrueNAS host environment.
A managed platform would have been easier.
Hosting an Astro site through a managed provider would require much less infrastructure. I deliberately chose the self-hosted route because operating the deployment environment was part of the project.
DNS, public ingress, port forwarding, TLS and reverse proxy routing become real parts of the application architecture.
Application packaging, registries, Compose stacks and container lifecycle management become practical operational concerns.
Development and production are connected through an automated build and deployment process rather than manual file copying.
Availability, health checks, rollback strategy and service exposure become my responsibility rather than the hosting provider's.
A portfolio that demonstrates its own infrastructure.
The final result is more than a frontend project. A change made in my local development environment can move through source control, automated build infrastructure, a container registry, my homelab and the public reverse proxy before appearing on the live site.
The same infrastructure also ties directly into the interactive Homelab page, which documents the environment this application is actually running inside.