This site is a static website, a custom booking system and a mail server, all running on a server I rent from Oracle for exactly zero pounds a month. This is how it fits together, including the bits that broke.
Why Astro
Most of this site is text and images. It doesn’t need a framework running in your browser just to show you words. Astro builds everything into plain HTML (HyperText Markup Language) ahead of time, and only sends JavaScript for the parts that actually move.
Those moving parts are called “islands”: small interactive bits floating in a sea of static page. The card stacks, the scroll wheel and the globe are all islands.
There’s a small irony here. One of the projects on this site, CrewSpace, is a full React app. This site ships no React at all. The islands are plain JavaScript, which keeps everything light and means I never have to deal with a React upgrade on my own portfolio.
The bits I’m quietly proud of
The Safari window. Videos of my work play inside a fake Safari browser. The frame is an SVG (Scalable Vector Graphics, basically a drawing made of maths) with a window cut out of it, and the video plays behind the hole. It sounds fussy. It is fussy. It’s also the only way I found to stop the video’s corners glitching.
It gave me my favourite bug, too. With two Safari windows on one page, both used the same internal name for their cut-out, so the second window borrowed the first one’s hole. Every window now gets a random name. Problem solved, dignity mostly intact.
The globe. The Life section has a spinning globe that marks where you are. It doesn’t ask for your location. It reads your device’s timezone and places a marker at a matching city. Roughly right, completely private, and nobody gets a permissions popup.
The videos. They don’t load until you’re close to them, only play when you can actually see them, and pause when you scroll away. If your phone blocks autoplay, you get a play button instead of a black rectangle.
The bits that broke
The scroll progress bar vanished in production. It worked perfectly on my machine, which is the most suspicious sentence in software. The build tool that shrinks CSS (Cascading Style Sheets, the file that makes things look the way they do) was helpfully merging two settings into one shorthand that browsers don’t understand, so they quietly threw the whole thing away. For now, that shrinking is switched off. It’s not elegant, but the bar is back.
The booking form and Google. The booking backend runs on Google Apps Script. Before certain requests, browsers send a quiet “am I allowed to talk to you?” check, and Apps Script doesn’t answer it. The fix is sending the data as plain text, which feels wrong and works perfectly.
Every time I updated the script, I also had to publish it as a new version of the same deployment, not a new deployment. A new one gets a new web address, which the website knows nothing about. I learned that the usual way.
Booking a call
The booking widget is custom built rather than a Calendly embed. Partly to avoid another subscription, mostly because I wanted to prove I could build one.
- The site asks Apps Script for free slots on my calendar.
- You pick one and fill in the form.
- Cloudflare Turnstile checks you’re not a bot, invisibly. No traffic lights to click.
- Apps Script checks the slot is still free, locks it so two people can’t grab it at the same moment, and books it through the Google Calendar API (Application Programming Interface, the door one program uses to talk to another).
- Creating the calendar event also generates a Google Meet link.
- You get a confirmation email from
hello@akrammh.com, and a reminder before the call. - A sweep runs every ten minutes to send reminders, instead of scheduling a separate timer for every booking.
Eventually I’d like the calls to run on my own server too, using Jitsi, an open-source video calling tool. Everything else here is self-hosted, and Google Meet is starting to look like the odd one out.
The emails go through my own mail server, which is a whole post of its own: Running my own mail server on Oracle.
Hosting
The site is built inside Docker, which packages it with everything it needs so it runs the same everywhere. Caddy sits in front as the web server, and it sorts out HTTPS certificates by itself, which is one less thing for me to forget to renew. All of it runs on an Oracle Cloud server.
When I push changes, a small Python script on the server notices, pulls the new code, rebuilds and restarts the container. No hosting platform, no monthly bill.
The footer says that if the site is down, that’s probably next week’s blog post. That wasn’t entirely a joke.