← All projects

Portfolio Website

The self-hosted Express application behind this portfolio, running on my own Raspberry Pi with containerized deployment and automated production updates.

Overview

This is the website you're looking at right now. I built it as my own portfolio rather than using a hosted portfolio platform, which gave me control over both the website itself and the infrastructure behind it. The site is a server-rendered Node.js application built with Express and Handlebars, packaged with Docker and served through Caddy. The production system runs on my own Raspberry Pi, making the hosting, deployment and maintenance part of the project as well. Over time, it has become a project in maintaining a small production web application, with automated updates, persistent configuration and a test suite covering important application behavior.

Architecture & Implementation

The portfolio is built as a server-rendered Express application, with Handlebars used to generate the pages shown in the browser. The application is structured around separate routes, controllers, middleware and supporting modules, which has made it easier to expand the site while keeping the main application entry point relatively small.

flowchart LR
    browser([Browser]) --> caddy

    subgraph Pi[Raspberry Pi]
        caddy[Caddy reverse proxy] --> app[Express + Handlebars]
    end

    subgraph Deployment[Deployment]
        actions[GitHub Actions]
        image[Docker image]
    end

    actions --> image
    image -->|Pulled and deployed| app

I chose to host the site myself instead of relying on a managed hosting platform. The production environment runs on my own Raspberry Pi, with the application running in Docker and Caddy acting as the public-facing reverse proxy. Caddy handles HTTPS, redirects and production security headers before forwarding requests to the Node.js application. Running the complete production environment myself has also made server configuration, deployment and maintenance a practical part of the project rather than something handled by a hosting provider.

Static files such as images, stylesheets and browser-side JavaScript use content-hashed URLs generated through a Handlebars asset helper. This allows them to be cached for long periods while still ensuring that a changed file receives a new URL and is downloaded instead of being replaced by a stale cached version.

The deployment process was gradually automated as the site developed. Application images are built through GitHub Actions and published as container images, allowing the Raspberry Pi to pull and run an already-built version rather than rebuilding the application itself. Updates can then be applied automatically while keeping runtime configuration and persistent files separate from the application image.

The project also includes a Jest and Supertest integration test suite that runs the real Express application in-process. Rather than testing isolated functions only, the tests can send requests through the application and verify how routes, middleware and error handling behave together. This has been particularly useful as the portfolio has grown beyond a collection of mostly static pages.

Alongside the public portfolio, I also use the same web application privately for personal purposes. Authenticated and admin-only functionality lets the application serve as a small personal platform as well, while keeping those parts separate from what a normal visitor sees.