The new jasper.tandy.is
I feel like if you're being honest with yourself, there are things that you know, but you could know better. I have a lot of those things. I work with people who are really good at what they do, and it's very easy for me to just let other people do things. Delegation is fine, but it does mean you miss out on a lot of opportunities to learn new skills. You also miss out on a lot of opportunities to become so stressed that your head explodes, so it's not all bad.
Something else that I haven't done on this site for a very long time is roll someone else's platform. The last version of this site was using a self-made CMS on Rails. I love Ruby, and I love Rails. They are the epitome of they thought of everything, and I've always found them to just make sense in a way that no other language or framework really has in my entire career.
That being said, I just finished a project that is built partially on Buffalo's current CMS of choice; Craft. I have struggled to think of a failing in other CMSes that Craft doesn't address or solve completely, and I haven't thought of one. If you're being harsh, the database structure isn't my favourite, but they've taken the modern CMS database structure approach and turned it into an asset, rather than something that makes me want to hit myself with a hammer, so I'm not counting it. It also has a brilliant way of keeping your development changes in sync with production, which nothing else really solves without manual migrations.
Before I go into more detail, the stack I'm currently using for this site is a Next.js frontend (mostly - I'm still using Craft to generate my RSS feed), Craft backend with their native GraphQL API, hosted on Linode with Docker and nginx, deployed with Gitlab CI.
The Next/GraphQL side of this is heavily based on Next's with-apollo example. The only change I had to make was to switch from Heuristic to fragment caching (and write all my specific component logic, obviously) because apparently that just doesn't work with Craft/GraphQL.
First you need to use GQL to introspect your fragments (I found this script somewhere; I don't remember where - if you wrote it, tell me and I'll add your name to it):
Then modify apollo.js to use this new cache:
And that's it; you're basically ready to get started with actually building your components. This isn't a React tutorial so I'll spare you my Dunning-Kruger-fuelled React advice.
Perhaps the most important foundational element of this (that I, surprisingly, didn't lead with) is the nginx setup. The primary rule of this setup was that everything runs from one domain. I didn't want to have to use feed.jasper.tandy.is for my RSS feed and admin.jasper.tandy.is to get access to my admin UI. That's gross. So nginx had to play the part of proxy here. nginx makes this ridiculously easy. Craft/Yii tries to make this difficult, but you can get around this with an evening of scrolling through Github source to try and figure out a way to force Yii to use the right baseURL for things, or by reading this blog post to know you just override the Host header when proxying. I've also set up some caching between nginx and GQL to reduce the load on Craft. Craft already employs caching, but nginx caches are ridiculously performant, in my experience so thanks Craft but we got this.
And that's pretty much it. Aside from a lot of boilerplate, nginx is now playing switchboard operator.
And, finally, the interesting bit. There's a quote about Regular Expressions, which goes something like "OK, you have a problem you've decided to solve with regular expressions - well, now you have two problems". This logic, when applied to Docker would conclude with 15-20 problems. That's not to say that Docker is problematic; it's actually brilliant, but it's one of those things where every time you think you've figured a bit of it out, there's another, more complicated concept waiting to trip you up.
It's way outside the scope of this to talk about what Docker is and how you should use it, so I'm not even going to start. If you don't know what Docker is, either skip this next bit, or read this, then read about docker-compose, then come back. Suffice it to say that Docker makes adding dependencies and having them communicate a relative breeze, once it's up and running or you have a good understanding of how to implement it.
So, broadly speaking, I'm using Docker to make it easy for my nginx proxy to redirect your requests to where they need to go. Docker handles all the networking for me, so I can use its internal DNS to know that http://craft points to my Craft container, and http://frontend points to my Next one. This makes it much easier to use Gitlab's container registry with Gitlab CI to push code around. Rather than having to have my CI runners SSH to the target host, clone stuff and run migrations, they can simply connect to the target host's Docker daemon and pull down containers that have already been built.
Aside from my build and publish steps (build is responsible for running
docker build ..., and publish is responsible for tagging
latest), the deploy steps are the most interesting. I have two deploy steps - one automatic and one manual. The automatic one handles accessing the remote docker daemon, pulling my new images down and restarting containers whose base images have changed. The manual one clears out my GQL cache. This is one that is pretty much never necessary, but when it is necessary, I really need to be able to do it.
And there you have it. The only thing that
deploy_production does additionally to pulling down my built images, is nudge my Craft
project.yaml to pull in any changes (why Craft 503s on the frontend here, I do not know, but at least they provide a tool to mitigate this). This saves the momentary downtime where I would have to log into Craft and accept those changes. On other projects, I might run migrations here, or any cache warming. Baby steps, though.
I've found this little project to be a lot of fun, and I've learned a load about stuff I already knew a bit about, and stuff I never knew about but probably should. One of my favourite things about having this site has always been that it's a great learning tool for me, as well as a platform for me to be able to share whatever I'm thinking about or pointing my camera at, whilst still retaining ownership of it all.
I did realise whilst writing this, that the reason I thought this post was necessary was because I did so much messing around to get everything working that it seemed like there was a lot more to this than there actually is! Still, I've written it now, so might as well keep it. If you have any questions about any of this (or maybe you want to call me an idiot because I'm doing something ridiculously wrong - I would appreciate the help, but not the rudeness!), then please feel free to get in touch with me.
Thanks for reading, if you did. And if you didn't, thanks for thinking this might be something worth reading!