Sometimes I want to write a bit of code to automate a task. This can be anything, but a common pattern is a script that periodically wakes up, checks the state of the world, and performs an action. Think of Zapier, IFTTT, or Airtable Automations, but custom code.
The first time I encountered this problem, I solved it the way I would solve it professionally. I used AWS Lambda and the rest of the AWS ecosystem.
- Made an AWS IAM role scoped to the permissions needed by the Lambda.
- Created a Lambda function and configured it to use the AWS IAM role
- Configured CloudWatch to trigger my Lambda on a schedule.
- Created an SNS topic and configured a CloudWatch alert on Lambda errors, for monitoring.
- Wrote a build script that creates a zip bundle of my Python code and related dependencies.
- Wrote a deploy script that uploads the zip bundle and updates the Lambda using the AWS CLI tools.
- Wrote a local test script that assumes the Lambda function’s IAM role before executing my code.
- Finally, I considered using Terraform or CloudFormation to manage all of the AWS resources, but deferred that to future work. I also didn’t set up CI/CD. I’ve used CodeBuild before, and have no desire to use it again.
I’ve used these technologies before, so I knew how to glue them together without a lot of extra googling. Even so, I still spent 10x more time setting up this infrastructure compared to writing the code itself. The project was cloud-native, serverless, fault-tolerant, scalable, distributed, elastic, and way over-built. I didn’t need any of these properties.
Similar to how I decided to use WordPress since maintaining my static site generator was sucking the joy out of blogging, I decided that using AWS was sucking the joy out of writing small side projects. Optimizing the coefficient of an O(n) scaling problem doesn’t matter if my n=1. I didn’t want cattle, I wanted a pet.
Enter: the fluffy and adorable home server. Thanks to Moore’s Law and the dominance of mobile phones, a home server is no longer a hulking ATX full tower or still-ungainly HTPC. Instead, it looks a lot like a Raspberry Pi 4 nestled next to a router.

In fact, my home server looks exactly like that, since this is a picture of my home server. For $90, I got a quad-core ARM processor with 8GB of RAM, 4K video out, and four USB ports, in a form factor that fits in a pants pocket. I scrounged the MicroSDXC card, USB-C cable, and wall wart from my box of random electronics (everyone has a box like this, right?).
This was my easiest PC build ever. I was looking at a blinking bash prompt within an hour of unboxing, which included installing the Pi in its case, downloading and flashing Raspbian and figuring out how to do a headless installation.
Writing my first script was equally easy: Vim, Python, tmux, and watch(1). I wrote a fifteen line Python script inside a tmux session, and started it with watch -n 600 ./run.py
. And that was it! I went from a pile of parts to a working automation in an evening of tinkering. Pure joy.
I’ll check the script into git later, but this is otherwise a perfectly splendid workflow. Testing the script? Just run it. Re-deploying? Ctrl-C and up+enter to re-run the watch
command. Debugging? Stdout is printed to the tmux session. When programming for an audience of one, I don’t need the accoutrements of AWS. Honestly, I probably don’t even need version control, but some habits are hard to break.
This experience is also a testament to the wisdom of the Unix programmers of yesteryear. 30 years ago, they were using vi, Perl, and screen instead of Vim, Python, and tmux, but the core development workflow has remained unchanged. And watch(1)
remains an eternal classic.
I plan to show some of my scripts in a future post, but for now, let this be my ode to the simple pleasure of a home server. It’s the perfect platform for tinkering at home, and there’s no substitute quite like it.