Deploying Go CLI Applications

Ben
7 min readMar 4, 2020

--

Image courtesy of GoReleaser

Recently, I was working on an internal CLI application that will improve the workflow of checking in for a class at my school and wanted an easy way to deploy my application for as many people as possible to be able to download it quickly with no hassle to them. Since most of the developers here use either Mac / Linux, homebrew seemed like a very safe and easy bet as the best way to get what I was looking for.

GoReleaser

Through a bit of research into the best way to deploy a Go CLI application to homebrew, I stumbled upon GoReleaser. GoReleaser is a release automation tool for Go projects, with built-in Homebrew support which is exactly what I needed! Their goal is to simplify the build, release, and publish steps while providing customization of the process when available. With over 5000 stars as of the time of me writing this, it seemed like the obvious choice for me.

Installation

I am not going to go super deep into the installation process as the official GoReleaser docs do a pretty good job of explaining it, but I will go over a simple installation guide that should cover most basic CLI applications with non-complex dependencies.

GoReleaser install instructions

Quick Start

Once you have GoReleaser installed, releasing your application should be a very straightforward process that should only take a couple of steps.

The first thing you are going to need to do is to initialize your project with a new GoReleaser config file. To do this, all you need to run is a single command and it will generate a barebones default config file in your project directory.

goreleaser init

Barebones GoReleaser configuration file

This command should have created a .goreleaser.yml file in your project directory, which you can make changes to or leave it as is. The file should look something like the one on the left. In theory, this its self should be able to get you all up and running with a single command, but I like to make a few customizations to the process before I do any deployment. If you want to skip down to the deployment section and skip any customizations, feel free to scroll down to the Deployment subtitle.

GoReleaser offers quite a few customizations when it comes to the build process, so many so that I do not have time to cover much so I will only be discussing a small subsection of them. The base configuration that I like to build from is as follows. Through this configuration, I am able to quickly build GoReleaser files that allow me to deploy larger-scale complex applications easily.

My base GoReleaser config file

Deployment

Now that you have your configuration file all setup, you are able to do a trial run of your deployment process. This is as easy as running a command with a few flags that will stop you from releasing to Github. The command to do this is as followed.

goreleaser --snapshot --skip-publish --rm-dist

The build will probably take a minute, but if all goes well then you should have a message telling you that your build was successful!

From here, you probably want to build and release your application on your Github repository, as well as make your application easily accessible via a Homebrew tap.

The next step to deployment is creating a personal access token to use with GoReleaser. You can do that here. https://github.com/settings/tokens/new

Once you have your access token, deploying to your Github repo is as simple as a few steps. First, you are going to need to create a version of your application and push that to Github. This will allow you to push your code and create a release. Run the following commands and change the version to be whatever you want.

git tag -a v1.0.0 -m "First release" && git push origin v1.0.0

Finally, you just need to export that Github personal access token that you got earlier, and deploy!

export GITHUB_TOKEN=YOURGITHUBTOKENHERE; goreleaser --rm-dist

The above command should have had a similar success message as your dry run. In theory, you should be able to check your versions page on your Github repo https://github.com/<github_username>/<repo_name>/releases and see your most recent release.

Example of a

Homebrew

Once you have normal deployment working, creating a Homebrew tap is a very easy process that only involves a bit of config editing and a new Github repo if you want to go the route of having an easy install for your users.

The most basic config change you need to do is as followed. Notice how I have comments about either same repo or sperate repo deployment. I would recommend creating a new empty repo and doing separate repo deployment so that your tap command is nicer for your end-user.

Two examples of different Formula locations.

Once you have chosen if you want to deploy to a separate repo or the one with all of your other code, and choose the snippet of code above and add it anywhere to your.goreleaser.yml file.

Depending on your choice, next time you run goreleaser --rm-dist GoReleaser will either add the new Formula to either the new repo that you created or to your current repo.

If you choose the former option, tapping and installing your new application is as easy as two simple commands, and then you can run your compiled binary that we specified earlier!

brew tap <github_username>/<github_repo>
brew install <github_repo>
<binary_name>

If you chose the latter option, tapping and installing your application is still incredibly easy, but the commands are just a bit more verbose.

brew tap <github_username>/<github_repo> \  https://github.com/<github_user>/<github_repo>
brew install <github_repo>
<binary_name>

That is all it takes to get your CLI application installed and set up as a homebrew tap!

Future Releases

Now that all your configuration is out of the way, creating future releases is incredibly easy. All you need to is push your code, create a new tag, and push it your Github repo.

From there, all you need to do is run goreleaser --rm-dist and everything else should be taken care of for you!

Using Cgo or Go packages that rely on C

When I was deploying my personal project that got me into this in the first place, I was having a ton of issues with GoReleaser and I had no idea why. Through a ton of investigation, I found my issue was that the keychain library that I was using had a reliance on C code, therefor having a reliance on Cgo.

What is Cgo you may ask? Cgo enables the creation of Go packages that call C code, but through doing this you have to enable additional support for GoReleaser.

The first thing I tried was just enabling Cgo support in my.goreleaser.yml by simply setting CGO_ENALBED=1 in my GoReleaser environment variable config file. Unfortunately, this did not work due to missing different build dependencies, and the library that I was using not working. I learned that this was due to GoReleaser not supporting Cgo, and they will not be supporting it in the future. If you are interested, you can read more about that here.

So after more trial and error, including switching the keychain library that I was using, I stumbled across a great article from Rob De Feo where he used Docker to solve the exact problem that I was having. I will not explain everything that he did because you should go read his article, but I will give the quick solution here with little explanation. Here is a link to his article.

If you do not want to read the article, you are going to need to do some more configuration stuff as well as running the command from within a custom docker image. The command you will need is as followed.

The quick rundown of what this is doing is allowing your application to access all the ‘stuff’ that C requires to build and compile across different OS’s allowing you to use Go packages that rely on C code.

The only other thing you need to change is your configuration file. Instead of going through everything that you need to change, it would be easier for you to see a working application that I have configured to use Cgo and compare it to the base configuration that I gave you earlier.

https://github.com/tempor1s/msconsole/blob/master/.goreleaser.yml

Once you change over your application to one that has Cgo enabled, and you have enabled the correct environment variables, your application that has C dependencies should now be able to built using GoReleaser.

Closing Notes

I hope you guys all enjoyed this article and have found it useful when deploying your Go projects. I just want to thank everyone I have pulled resources from fro this article, with a special thanks to Rob De Feo for his work on the GoReleaser Cgo Dockerfile.

If you want to check out other work that I have done, feel free to check out my Github or LinkedIn.

If you have any questions, comments or just want to chat, feel free to reach out to me. Hope you have enjoyed reading!

--

--