Packaging a Go application for macOS

This is a quick guide about how we packaged up Relica (a cross-platform backup service written in Go) for macOS. You can follow this tutorial to package up a Go program — with or without external resources — for macOS. It can then be downloaded, installed, and run as if it were a native Cocoa app. It does not require XCode, cgo or any special libraries.

By the end of this tutorial, your Go program will be bundled into a .app file and distributed in a read-only DMG that has the nice “drag-and-drop” install. This also works for non-Go applications.

There are other tutorials out there for pieces of this, and end-to-end solutions that did way more than I wanted or in a different way, but I wanted full control of knowing what and how things got packaged up.

Requirements: You need macOS to complete this guide. It won’t work on Windows or Linux. Actually, it could work, but you’d need to craft some macOS-specific stuff like DMG and .DS_Store files either by hand or with the help of some crazy tool.

Make an icon for your app

You’ll need a high-resolution icon; preferably something beautiful. It needs to look great on light and dark backgrounds. See Apple docs for details.

Save it as a transparent PNG with dimensions 1024x1024 for best results.

Here are some examples:

Example icons for apps that come with macOS.

Compiling an icon set

Once you have your big icon, you’ll need to save the icon in different sizes and resolutions. The recommended list of sizes for full compatibility are 16, 32, 64, 128, 256, 512, and 1024, with @2x variants of each one for hi-DPI screens (which is basically all of them these days) except 1024. This can be tedious to do unless your graphics program exports them for you, or you can script the resizing with a tool like sips:

$ sips -z $SIZE $SIZE myicon.png \
--out myicon_${SIZE}x${SIZE}.png

And so on. Don’t forget the @2x versions, which are the next size up but labeled as the current, half-size of the next. Yes, it’s a lot of work, but this step can be automated, and you only have to do it once. (The end of the article links to a sample program.)

Then, assuming your icons are in a folder called “myicons”, make the icon set with iconutil:

$ iconutil -c icns -o icon.icns myicons.iconset

Make the .app bundle

A launcher-runnable macOS application is nothing more than a .app folder with a binary and a manifest inside. You can even make it manually. It has a simple folder structure:

$ tree Caddy.app
Caddy.app/
└── Contents
├── Info.plist
├── MacOS
│ └── caddy
└── Resources
└── icon.icns
3 directories, 3 files

The key files are:

  • Info.plist: Basically a manifest. Copy, paste, and customize the values.
  • caddy: The binary you’re bundling.
  • icon.icns: A bundle of icons of various sizes.

(To browse a .app folder, right/Ctrl-click and choose “Show Package Contents.”)

Add the Info.plist file

Here’s a template you can modify as the contents of your manifest (I stole this from Dmitri Shurylov — thanks buddy!):

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleExecutable</key>
<string>caddy</string>
<key>CFBundleIconFile</key>
<string>icon.icns</string>
<key>CFBundleIdentifier</key>
<string>com.example.yours</string>
<key>NSHighResolutionCapable</key>
<true/>
<key>LSUIElement</key>
<true/>
</dict>
</plist>

Make sure to replace the CFBundleExecutable and CFBundleIdentifier values.

NOTE: The LSUIElement key tells the OS that your application is an agent app, so it will not appear in the dock. This is important, otherwise it’ll keep bouncing in the dock forever unless your Go program is designed to respond to events in the Mac event loop like Cocoa programs are.

Congrats! You have a .app bundle for your Go program. Double-clicking it should run it. You can even drag it to the Applications folder and have it appear in your Launcher like a native Cocoa app.

Speaking of which, let’s make that easy to do from a distributable DMG file.

Making the DMG file

The DMG file is how you should distribute your application. It compresses the entire .app folder and makes it easy to drag it into the Applications folder for installation.

This tutorial will show you the manual way of doing it, but the gist at the end will show you how to automate it.

Making a template DMG

The template need only be made once, which is good because it’s hard to automate this and get nice results.

Open Disk Utility. Press ⌘N to create a new disk image. Give it a name and a size that is large enough to fit your app bundle:

Set the filename, name, and size, but leave other fields the same.

Go to your mounted image in Finder. Customize the view settings for this folder so that it looks just the way you want when users mount their DMG to install your application. Consider setting a background image, hiding the toolbar and sidebar, and increasing the icon size.

NOTE: A background image must be contained within the DMG itself. This is usually done in a folder called .background. Because it starts with a dot, it will be hidden from view. Put the background image in there, and set it by dragging it into the View Options:

My almost-finished DMG template. Notice the View Options on the right. Most of them were customized: larger icons and text, always open in icon view, set a background, etc. I have yet to hide the sidebar and toolbar. Also note that the background image is stored inside the DMG in a hidden folder.

You’ll also need a shortcut (“alias”) to the /Applications folder in your DMG for convenience. You can right-click your own /Applications folder and choose “Make alias” then move it into your mounted image:

Make an alias of your /Applications folder (usually the system one; not the one in your Users directory) and copy it into your DMG. Place it like shown above.

Now we just have to add our app bundle into the DMG and finish customizing the view:

Great! Our template DMG is all set up. Now we can get it ready to distribute.

Converting the DMG for distribution

The current DMG is not compressed and is writeable. That’s not ideal for distributing an application, so we’ll fix that by converting it.

Open Disk Utility and choose Images → Convert. Name the file something presentable, and leave the rest of the settings the same. (Image format should be “compressed”.)

And voila! You have an archive ready to distribute. When you open it and drag the .app into your Applications shortcut, it will appear in your launcher:

“Oh, it’s beautiful!” … if a little flat. :)

Making new images later

In the future, you can reuse your template DMG and icons. All the painful stuff only had to be done once. To do a new release, you just have to re-make the .app bundle (can be automated), replace it in the template DMG (can be automated), and re-convert the DMG for distribution (can also be automated).

Automating it

Some of the one-time steps above can be automated (like creating the icon set). Some, however, cannot easily be automated with nice results (for example, customizing the view of the DMG). Fortunately, it is easy to automate the rest, including the steps you take every time you re-package a new release. The hdiutil command can be of help when working with creating, mounting, and converting images.

You can see a sample Go program I wrote that automates most of this. It will create the .app bundle and then optionally create the final .dmg if you give it a template .dmg. It even creates all the different icon sizes for you by shelling out to the commands mentioned above. It does all the copying of files and mounting, un-mounting, and conversion of images. And you can change anything you don’t like or that doesn’t work (quite likely to happen, haha). It’s pretty well-commented, so feel free to read it. At the very least, you will have to give it the folder where your binary is and any resources associated with it, the name of the binary itself, the 1024px-sized icon file, and the human-presentable application name.

It’s not an open source project that I plan to maintain, but you may use it to translate what you’ve learned here into code and apply it to your own use cases.

I think it’d be cool to have more Go projects that ship like apps. Go code is beautiful, why not their compiled binaries as well?


(Since I’ll inevitably get questions about this: no, I’m not planning on packaging Caddy like this in the near future; but maybe someday. I was figuring this out for another application I built called Relica — a file backup program that lets you delete your files and have them, too.)