# VM-02

portfolio-server

Public self-hosted portfolio.

Overview

Hosts this portfolio website and engineering documentation.

Runtime Architecture

Visitors
│
Cloudflare DNS
│
Cloudflare Tunnel
│
Proxmox VM
│
Portfolio Container

Infrastructure Architecture

Proxmox VM:
└── VM-02 Portfolio Server
    ├── Portfolio Website
    ├── Cloudflare Tunnel
    ├── GitHub Runner
    └── Node Exporter

Deployment Pipeline

VS Code
│
git push
│
GitHub Repository
│
GitHub Actions
│
Build Image
│
Container Registry
│
GitHub Runner (Portfolio VM)
│
Pull Image
│
Portfolio Container

Challenges

  • Deployment Completed but Site UnchangedAlthough the CI/CD pipeline completed successfully, the latest changes were not visible on the live website. The container replacement process appeared healthy, but the running container was still serving old cached content.
  • Connecting a Self-Hosted Application to a Public DomainThe portfolio was running internally inside the local network, but exposing it securely to the internet required configuring DNS records, SSL/TLS certificates, and routing traffic to the correct container service without opening ports on the router.
  • GitHub Runner Unable to Pull the Latest Container ImageThe deployment workflow steps failed during the image pull stage because the self-hosted runner running inside the VM could not authenticate with GitHub Container Registry (GHCR). As a result, the deployment process stopped before the updated application could be deployed.

Solutions

  • Fixing Image Source MismatchWhen investigating the deployment script, I discovered that the deployment script was pulling an image from an older repository instead of the current portfolio repository. Updated the deployment configuration to reference the correct container image and verified that the workflow was pulling the latest build before restarting the service.
  • Implemented Cloudflare Tunnel for Secure Public AccessInstead of exposing the server through port forwarding, I configured Cloudflare Tunnel and mapped the service to a custom domain. This provided HTTPS by default while keeping the home network completely hidden from the public internet.
  • Configured Authentication for GHCR AccessI generated a Personal Access Token (PAT) with the required package permissions and configured it on the server, allowing the deployment workflow to authenticate with GitHub Container Registry, ensure the runner was able to access GHCR successfully, and complete the deployment process.

What I Learned

  • Always verify the image source and deployment target when troubleshooting CI/CD pipelines. A successful deployment status does not necessarily mean the correct application version is running.
  • Docker containers, container registries, and self-hosted runners work together to create an automated deployment workflow.
  • Cloudflare Tunnel provides a secure alternative to traditional port forwarding for self-hosted applications.
  • Building and maintaining a homelab environment provides practical experience with Linux, networking, containerization, and DevOps workflows.
  • Troubleshooting production issues often requires validating every step of the deployment pipeline rather than assuming the reported status is correct.