Built from scratch · kept deliberately small

About this site

I built this portfolio to be boring in the best way: one Go binary, explicit routes, embedded pages and images, and very little infrastructure to surprise me later.

Code
Go 1.26.5 · chi · Go templates
Runtime
One binary · one container · one droplet
Content
Go constants · embedded assets
External I/O
Brevo · contact form only

Small on purpose

This is a personal portfolio, not a miniature platform. It does not need a CMS, database, queue, or reverse-proxy tier, so it has none. That makes it easier for me to understand, deploy, and keep secure.

Page copy is compile-time Go data. Templates, the gallery catalog, and asset hashes are prepared once at startup; generated images and compressed assets are embedded in the binary. The first render of each known page is then cached in memory.

The only outbound request is the contact form’s transactional email call to Brevo. Everything else is the server returning local bytes.

Implementation notes

Under the hood

The short version is above. These four sections cover the implementation details without turning the page into a wall of infrastructure notes.

From request to response One process, explicit routes
  • One process handles routing, TLS, templates, static files, caching, and the contact endpoint. There is no proxy tier in front of it.
  • Production traffic has two deliberately narrow entry points:
    • :80 handles Let’s Encrypt HTTP-01, serves /ping, and redirects everything else to HTTPS.
    • :443 serves the application over autocert-managed TLS 1.3.
  • Startup does the reusable work: templates and the email template are parsed, the gallery is discovered and sorted, assets are hashed, and compressed variants are indexed before the listeners open.
  • Known page routes are cached after their first render. The cache key follows the router’s cleaned path and can only grow to the fixed pages plus valid album IDs.
  • The code stays separated: models own content and validation, controllers coordinate requests, views render templates, and infrastructure owns transport and deployment.
  • Canonical URLs always point at milosmijic.com; configured aliases such as www redirect permanently.
Security choices Strict defaults, narrow exceptions
  • Transport: Let’s Encrypt certificates, TLS 1.3 as the minimum, and two-year HSTS with subdomains and preload enabled.
  • The browser starts from no permissions: the CSP begins with default-src 'none' and opens only the sources this site uses. The small inline theme bootstrap is allowed by its exact SHA-256 hash.
    • Trusted Types is required for scripts; inline script and style attributes are blocked.
    • Framing is denied, MIME sniffing is disabled, referrers are limited, and camera, microphone, and geolocation permissions are turned off.
    • COOP and CORP stay same-origin; COEP remains off because this site does not need cross-origin isolation.
  • The contact form gets the strictest path: same-origin checks, scoped CSRF tokens, a 128 KiB body cap, field validation, a honeypot, five attempts per IP per 15 minutes, and a site-wide budget of 30 validated sends per 15 minutes.
  • Hosts and paths are normalized early. Unknown hosts are rejected, configured aliases redirect to the canonical host, panics are recovered, and handler time is capped at 20 seconds.
  • Gallery media has a separate boundary: album paths are normalized and hotlink checks use Sec-Fetch-Site with a same-site Referer fallback.
Fast by default Build once, reuse often
  • Static work happens before requests: responsive cover variants and WebP gallery images are generated ahead of time; CSS and JavaScript receive Brotli and gzip versions during the build.
  • HTML is cheap to revisit: known pages are retained in a bounded in-memory cache, use weak content ETags, and return 304 Not Modified when the browser already has the current representation.
  • Static assets use exact validators: strong ETags describe the bytes actually served, including separate Brotli, gzip, and identity representations.
  • Versioned URLs are immutable for a year. Templates add content hashes to UI and gallery URLs; stable unversioned URLs revalidate instead of going stale.
  • The gallery sends the right-sized image: responsive cover srcset, lightweight thumbnails, larger lightbox files, reserved dimensions, lazy loading below the fold, and no runtime resizing.
Keeping it running Health, deploys, observability
  • The runtime is intentionally plain: one static Linux binary in a distroless Debian container on a DigitalOcean Droplet. The container has no shell or package manager.
  • Health is checked from both sides: Docker runs the binary’s loopback healthcheck, while an external monitor checks public HTTPS health, the HSTS header, Brotli delivery, certificate lifetime, and reports HSTS preload status.
  • Deploys keep a way back: the release script snapshots the current runtime and image, checks the replacement publicly, and automatically restores the previous release if the new one fails health checks.
  • Logs are useful without collecting messages: requests receive IDs and single-line logs; contact names, addresses, and message bodies stay out of application logs.
  • Production configuration is environment-driven. Docker injects the server-side values; the application does not open a dotenv file in production.