Go 1.24's "tool" directive

· 4 min

Go 1.24 added a new tool directive that makes it easier to manage your project’s tooling.

I used to rely on Make targets to install and run tools like stringer, mockgen, and linters like gofumpt, goimports, staticcheck, and errcheck. Problem is, these installations were global, and they’d often clash between projects.

Another big issue was frequent version mismatch. I ran into cases where people were formatting the same codebase differently because they had different versions of the tools installed. Then CI would yell at everyone because it was always installing the latest version of the tools before running them. Chaos!

Docker mount revisited

· 5 min

I always get tripped up by Docker’s different mount types and their syntax, whether I’m stringing together some CLI commands or writing a docker-compose file. Docker’s docs cover these, but for me, the confusion often comes from how “bind” is used in various contexts and how “volume” and “bind” sometimes get mixed up in the documentation.

Here’s my attempt to disentangle some of my most-used mount commands.

Volume mounts

Volume mounts let you store data outside the container in a location managed by Docker. The data persists even after the container stops. On non-Linux systems, volume mounts are faster than bind mounts because data doesn’t need to cross the virtualization boundary.

Behind the blog

· 4 min

When I started writing here about five years ago, I made a promise to myself that I wouldn’t give in to the trend of starting a blog, adding one overly enthusiastic entry about the stack behind it, and then vanishing into the ether.

I was somewhat successful at that and wanted to write something I can link to when people are curious about the machinery that drives this site. The good thing is that the tech stack is simple and has remained stable over the years since I’ve only made changes when absolutely necessary.

Here-doc headache

· 3 min

I was working on the deployment pipeline for a service that launches an app in a dedicated VM using GitHub Actions. In the last step of the workflow, the CI SSHs into the VM and runs several commands using a here document in bash. The simplified version looks like this:

# SSH into the remote machine and run commands to deploy the service
ssh $SSH_USER@$SSH_HOST <<EOF
    # Go to the work directory
    cd $WORK_DIR

    # Make a git pull
    git pull

    # Export environment variables required for the service to run
    export AUTH_TOKEN=$APP_AUTH_TOKEN

    # Start the service
    docker compose up -d --build
EOF

The fully working version can be found in the serve-init repo with here-doc.

Omitting dev dependencies in Go binaries

· 3 min

As of now, unlike Python or NodeJS, Go doesn’t allow you to specify your development dependencies separately from those of the application. However, I like to specify the dev dependencies explicitly for better reproducibility.

While working on link-patrol, a CLI for checking dead URLs in Markdown files, I came across this neat convention: you can specify dev dependencies in a tools.go file and then exclude them while building the binary using a build tag.

Python dependency management redux

· 7 min

One major drawback of Python’s huge ecosystem is the significant variances in workflows among people trying to accomplish different things. This holds true for dependency management as well. Depending on what you’re doing with Python - whether it’s building reusable libraries, writing web apps, or diving into data science and machine learning - your workflow can look completely different from someone else’s. That being said, my usual approach to any development process is to pick a method and give it a shot to see if it works for my specific needs. Once a process works, I usually automate it and rarely revisit it unless something breaks.

Periodic readme updates with GitHub Actions

· 5 min

I recently gave my personal blog a fresh new look and decided it was time to spruce up my GitHub profile’s landing page as well. GitHub has a special way of treating the README.md file of your repo, displaying its content as the landing page for your profile. My goal was to showcase a brief introduction about myself and my work, along with a list of the five most recent articles on my blog. Additionally, I wanted to ensure that the article list stayed up to date.

Auditing commit messages on GitHub

· 6 min

After reading Simon Willison’s amazing piece on how he builds a feature, I wanted to adopt some of the good practices and incorporate them into my own workflow. One of the highlights of that post was how to kick off a feature work. The process roughly goes like this:

  • Opening a new GitHub issue for the feature in the corresponding repository.
  • Adding a rough description of the feature to the issue.
  • Creating a feature branch off of main/master/trunk. If the feature is trivial or just a doc update, this step can be skipped.
  • Referring to the issue in every commit message as you start working on the feature:
    • Appending #refs <issue-number> to every commit message. This will attach the commit to the concerning issue on the GitHub UI.
    • Appending #closes <issue-number> to the final commit message when the feature is complete.
    • If you need to refer to an issue after it’s closed, you can still do that by appending #refs <issue-number> to the commit message. So a commit message should look similar to Feature foo, refs #120 or Update foo, closes #115. The comma (,) before refs/closes is essential here. I like to enforce it.

This pattern also works for bugfixes without any changes. Here’s an example issue that shows the workflow in action. Plus, I follow a similar pattern to write the blogs on this site as well. This is what a feature issue might look like on GitHub:

Automerge Dependabot PRs on GitHub

· 3 min

Whether I’m trying out a new tool or just prototyping with a familiar stack, I usually create a new project on GitHub and run all the experiments there. Some examples of these are:

  • rubric: linter config initializer for Python
  • exert: declaratively apply converter functions to class attributes
  • hook-slinger: generic service to send, retry, and manage webhooks
  • think-async: exploring cooperative concurrency primitives in Python
  • epilog: container log aggregation with Elasticsearch, Kibana & Filebeat

While many of these prototypes become full-fledged projects, most end up being just one-time journies. One common theme among all of these endeavors is that I always include instructions in the readme.md on how to get the project up and running - no matter how small it is. Also, I tend to configure a rudimentary CI pipeline that runs the linters and tests. GitHub Actions and Dependabot make it simple to configure a basic CI workflow. Dependabot keeps the dependencies fresh and makes pull requests automatically when there’s a new version of a dependency used in a project.

Health check a server with 'nohup $(cmd) &'

· 2 min

While working on a project with EdgeDB and FastAPI, I wanted to perform health checks against the FastAPI server in the GitHub CI. This would notify me about the working state of the application. The idea is to:

  • Run the server in the background.
  • Run the commands against the server that’ll denote that the app is in a working state.
  • Perform cleanup.
  • Exit with code 0 if the check is successful, else exit with code 1.

The following shell script demonstrates a similar workflow with a Python HTTP server. This script: