← all posts

How this site is built

A tour of the stack behind jackneff.dev — Flask and HTMX on a plain Linux box, with deploys that happen on git push.

September 3, 2026

This site is deliberately small. No framework-of-frameworks, no build step, no container orchestration. One virtualenv, one systemd unit, one nginx vhost.

The app

Flask with the app-factory pattern, Flask-SQLAlchemy for the handful of project rows, and HTMX for the tag filtering on the front page — the server returns an HTML fragment and HTMX swaps it in. The blog you're reading is just Markdown files with front matter, parsed at request time.

def create_app(config_class: type = Config) -> Flask:
    app = Flask(__name__)
    app.config.from_object(config_class)
    db.init_app(app)
    app.register_blueprint(main)
    return app

Pico.css and htmx are vendored into static/ — no CDN at runtime. Fonts are the one exception.

The box

A 1 GB DigitalOcean droplet running Ubuntu. gunicorn binds a Unix socket; systemd owns the process and restarts it on failure; nginx terminates TLS and proxies everything that isn't /static/.

location / {
    proxy_pass http://unix:/run/portfolio/portfolio.sock;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-Proto $scheme;
}

Let's Encrypt for the certificate, Cloudflare in front for DNS and caching.

Deploys

There's no CI. Deploying is git push to a bare repo on the server, and a post-receive hook does the rest:

git --work-tree="$WORK_TREE" --git-dir="$GIT_DIR" checkout -f "$BRANCH"
"$VENV/bin/pip" install -r requirements.txt --quiet
sudo systemctl restart portfolio
curl -fsS --unix-socket /run/portfolio/portfolio.sock http://localhost/healthz

If the health check fails, the push fails, and I know before I close the terminal.

That's the whole thing. It's boring on purpose.