Skip to content

Dispatches

Automating Releases with GoReleaser

GoReleaser

Shipping a polished release for a software project by hand gets old fast: building for multiple platforms, packaging archives, generating checksums, publishing GitHub releases, cutting container images, and updating a Homebrew tap is exactly the kind of repetitive work that should not depend on memory or heroics.

GoReleaser turns that whole workflow into a repeatable release pipeline that scales from your first CLI to a heavily used open source project.

Not Just for Go-based Project Releases

Despite the name, GoReleaser supports releasing for Go, Python, Rust, Zig, and TypeScript based projects.

Manual releases often seem manageable at first, then turn into a mess the moment users ask for macOS support, ARM builds, checksums, containers, or a one-line brew install experience. Maintainers end up writing ad hoc shell scripts, copying files into GitHub Releases by hand, and hoping the version embedded in the binary matches the git tag they just pushed.

GoReleaser solves that by treating release engineering as configuration. You describe what to build, package, sign, and publish, then let one command, or one CI job, do the same thing every time. It handles the boring parts well enough that you get to focus on your project instead of your release checklist.

How to Create Terminal Demos as Code with VHS by Charm

VHS

Manual terminal recordings tend to age badly. The timing is inconsistent, the cursor jumps, the window size changes between takes, and the one command you needed to correct means starting over. If you have ever tried to capture a polished CLI walkthrough for a README, release note, or docs site, you have probably spent more time re-recording than documenting.

VHS from Charm (a.k.a., Charmbracelet) fixes that by turning terminal demos into source code. Instead of screen recording your desktop, you write a small .tape file that describes the terminal session: window size, theme, typing speed, commands, pauses, screenshots, and output format. Then VHS renders the result into a GIF, MP4, WebM, or even a directory of raw frames.

There are two hard parts in terminal documentation:

  1. Capturing a terminal session that looks clean and readable.
  2. Keeping that session reproducible as the tool, docs, and CLI output evolve.

Traditional recording tools help with the first part, but not the second. A hand-recorded GIF is an artifact, not a build input. Once it drifts from reality, you either live with stale docs or record it all over again.

VHS treats terminal demos the same way we treat infrastructure, tests, and CI workflows: as code.

Git Submodules Deep Dive for Platform Engineering

Platform engineering teams face a recurring challenge: shared code. You have a library of Terraform modules that ten product teams consume, a set of Ansible roles that every configuration management pipeline needs, or a collection of CI/CD workflow templates that must stay consistent across dozens of repositories. The naive solution is to copy files between repositories, but then every fix requires propagating changes to every consumer by hand. Git submodules offer a structured alternative: embed one Git repository inside another as a tracked dependency with an explicit, auditable version reference.

This post covers how submodules work at the Git level, how to add and consume them, the day-to-day operations that platform engineers and DevOps practitioners need to know, CI/CD automation with GitHub Actions, and the common pitfalls that cause teams to abandon submodules prematurely.

Git submodules mental model: the parent repository stores a gitlink commit pointer, not the submodule's files. Git submodules mental model: the parent repository stores a gitlink commit pointer, not the submodule's files.

Lost in Shades of I'm Okay

This piece contains discussion of depression and hopelessness. Please read with care.

There is a particular meanness to depression.

It doesn't always arrive like a storm. More often it comes like summer heat: slow, saturating, difficult to argue with. It settles into the walls, into the body, into the space between one thought and the next, until everything feels heavy with it. The house seems to take it in. Even the light looks tired by the time it reaches the room.

I have known days when the floor felt like the safest place to keep my eyes. Old wood, scarred and splitting, honest in its damage. Floorboards don't ask anything of you. They don't expect performance. They don't require you to explain why lifting your head feels like lifting stone. Looking down became a kind of prayer then, if prayer can be made out of exhaustion. I studied every crooked crack as if it might tell me how to stay in one piece. Lost in shades of "I'm okay."

Shadow of the Cloud

Lyrics

SHADOW OF THE CLOUD

I mixed the tank while the house was still
Pouring a blessing from a five-gallon kill
White jugs stacked like a monument of salt
We did what we did, and it wasn’t our fault
The rows needed saving and the bank needed grain
So we fired up the trucks and a yellow plane
We learned young what the dirt wouldn’t give
And how a man goes broke just to watch the harvest live

     And you can scrub your hands ‘til the skin turns red
     But you can’t wash away what’s already dead
     It hangs in the air, sweet and low
     A ghost in the lungs that waits and grows
     Yeah, the fields turn green, and the debts get paid
     But we’re dying in the shadow of the cloud we made

T-shirt soaked where the nozzle leaked through
And a stain on my skin that I couldn't undo
The spray-rig humming like a funeral bell
Buying us a season in a chemical hell
Daddy said, "Son, hold your breath when you turn"
But he never said nothing 'bout the way it would burn
He’d wash his face with the garden hose
And wipe the death right off of his clothes

     Repeat Chorus

I don’t blame the men who taught me the trade
They were terrified of the debts they’d made
You can’t fight the weevil with a prayer and a plow
And you can’t feed a family on the here and the now
So you trade away your breath for a yield per acre
And you pray for some mercy from the undertaker

I left that dirt, but I brought it along
In the marrow of the bone, in the silence of a song
Now the air in my chest feels heavy and still
Like the bottom of a hollow or a tank meant to kill
It takes a whole lot of work to keep a field in the black
When the price of the yield is the shirt on your back

I still see those jugs, bleached out in the sun
Like rows of headstones when the day is done
We weren't out to change the world, or even claim a right
Trying to keep a family tree from catching fire at night

But the soil’s gone sour
And the leaves are thin
And I can still smell the sweetness
Settling on my skin

Writer: J. Ryan Johnson (BMI)
Copyright: © 2026 J. Ryan Johnson. All rights reserved.
Phone: +1 (407) 902-5419
Email: hello [at] tenthirtyam [dot] org

Audio Disclaimer

Lyrics: Original | Audio: AI-Generated

I am a songwriter and a musician, but I am not the voice meant to inhabit these verses.

I've used AI to bridge the gap for the concept demos, crafted to serve as blueprints that capture the genre, tone, and weary soul I hear for each song.

They exist as an invitation, offered in the hope that these lyrics will eventually reach the hands of an artist and storyteller who can bring them fully into the light.

Until then, they remain as they were born: quiet reflections on the grit and grace found just north of the county line.

Oh My Zsh on macOS: A Reference for a Clean, Maintainable Shell

% omz version
             __                                     __
      ____  / /_     ____ ___  __  __   ____  _____/ /_
     / __ \/ __ \   / __ `__ \/ / / /  /_  / / ___/ __ \
    / /_/ / / / /  / / / / / / /_/ /    / /_(__  ) / / /
    \____/_/ /_/  /_/ /_/ /_/\__, /    /___/____/_/ /_/
                            /____/

    master (061f773)

If you spend a large part of your day in a terminal, your shell stops being just a shell and starts becoming part of your development environment. On my Mac, that environment is built around Zsh, Oh My Zsh, the Spaceship prompt, and a small set of plugins that improve the things I do constantly: Git, GitHub, containers, Kubernetes, Terraform, Python, Go, and Ansible. The result is not flashy for the sake of being flashy. It is a shell that surfaces useful context quickly, stays out of the way when I am focused, and is still simple enough to maintain without turning ~/.zshrc into a junk drawer.

Managing Stale Issues and Pull Requests with GitHub Actions

Every open-source project eventually faces the same problem: issues and pull requests that were once active go quiet. A bug report with no updates in a year. A pull request that was never finished. A feature request that the contributor lost interest in. These threads accumulate over time, and before long a repository's issue tracker becomes a graveyard of items that no one knows are still relevant.

The actions/stale GitHub Action gives maintainers a way to address this automatically. It scans issues and pull requests on a schedule, labels anything that has gone too long without activity, warns contributors that it will be closed soon, and closes it if no activity follows. The whole process is configurable and runs without manual intervention.

This post covers what the action does, how to configure it, a real-world example from one of my own projects, and an honest look at the tradeoffs so you can decide whether it makes sense for yours.

Ignoring Files in Git with .gitignore

Every Git repository accumulates files that should never be committed: compiled binaries, dependency directories, editor configuration, operating system metadata, and local environment files that contain secrets. Without a mechanism to exclude them, every git status output and git add . command becomes a manual filtering exercise. The .gitignore file is Git's built-in solution to that problem, and understanding how it works end to end, including its pattern syntax, scope model, and debugging tools, eliminates a class of frustration that affects developers at every experience level.

Tracking Empty Directories in Git with .gitkeep

Git tracks files, not directories. That distinction is easy to overlook until you run into the consequence: an empty directory you create locally simply does not exist after a fresh clone. No staging, no commit, no push will capture it, because Git has nothing to work with. The .gitkeep convention is the widely adopted workaround for this behavior, and it requires nothing more than a single empty file placed inside the directory you need to preserve.