Contributing Tutorial

We are excited to have you join the Porter community! This tutorial will walk you through how to modify Porter and try out your changes. We encourage you to follow the tutorial before submitting your first pull request because this will help you get your environment set up and become familiar with how to make a change and test it out.

If you run into any trouble, please let us know! The best way to ask for help is to start a new discussion on our forum.

Try Porter

Before contributing to any open-source project, you should start with becoming familiar with the software first.

  1. Install Porter
  2. Run through the QuickStart

After this you should be able to answer:

  • What is Porter?
  • What is a bundle?
  • How do I make a bundle?
  • How do I install a bundle?

We are always iterating on our documentation. If something isn’t clear and you have read our documentation, let us know by opening an issue. Say what you were trying to learn about, where you looked, if you could find a relevant page, and what still isn’t clear. We can then answer your question and improve our docs so the next person has a better experience.

Setup Environment

First let’s get your computer setup so that you can work on Porter. You will need a few things.

We are improving our support for building Porter on Windows. In a few weeks, we will have it all working on any Windows shell. For now, if you are on Windows, please use Windows Subsystem for Linux (WSL). The rest of this tutorial assumes that you are inside your WSL distribution when installing prerequisites and executing the commands.

  • Git

    If you are new to Git or GitHub, we recommend reading the GitHub Guides. They will walk you through installing Git, forking a repository and submitting a pull request.

  • Go version 1.13 or higher

  • Docker

  • Make. You can install it with a package manager such as apt-get, or homebrew.

  • Mage

Add GOPATH/bin to your PATH

Porter relies on a few tools that will be installed in your $GOPATH/bin directory. When go tools are installed, they automatically are put into GOPATH/bin and in order to use them that directory needs to be included in your PATH environment variable. This is a standard Go developer environment configuration and will be useful for other Go projects.

  1. Open your ~/.bash_profile or ~/.bashrc file (depending on your OS you will have one or the other) and add the following line to the file:

    export PATH=$(go env GOPATH)/bin:$PATH
  2. Now load the changes to your bash profile with source ~/.bash_profile or source ~/.bashrc.

Install Mage

We are transitioning from Make to [Mage]. Installing mage isn’t strictly required, you can always run go run mage.go TARGET instead of mage TARGET. However having the tool saves typing and time!

Mage targets are not case-sensitive, but in our docs we use camel case to make it easier to read. Run the following commands to install mage:

$ go run mage.go EnsureMage
$ mage

This is a magefile, and is a "makefile for go". See

  configureAgent       sets up an Azure DevOps agent with Mage and ensures that GOPATH/bin is in PATH.
  ensureMage           Ensure Mage is installed and on the PATH.
  setBinExecutable     Run `chmod +x -R bin`.
  startDocker          Ensure the docker daemon is started and ready to accept connections.
  testE2E              Run end-to-end (e2e) tests
  useXBuildBinaries    Copy the cross-compiled binaries from xbuild into bin.

You know that your $GOPATH/bin is configured correctly if you see the output above listing the defined mage targets.

You can enable tab completion for mage as well, so that you can type mage t[TAB] and it will complete it with the name of matching targets.

  1. Install bash-completion if it isn’t already installed with either brew install bash-completion (macOS) or apt install bash-completion (debian/ubuntu) depending on your operating system.

  2. Copy the script to a local directory:

    cp scripts/ ~
  3. Open your ~/.bash_profile or ~/.bashrc file and add the following line to the file:

    source ~/
  4. Now load the changes to your bash profile with source ~/.bash_profile or source ~/.bashrc.

Checkout Code

Porter can either be cloned into your GOPATH (usually ~/go) or anywhere on your computer. Porter has many repositories. If you decide to not clone Porter into your GOPATH, you may still want to clone Porter and its other repositories into a dedicated directory so they are easier to find later.

git clone ~/go/src/

If you are planning on contributing back to the project, you’ll need to fork and fetch your fork. Here is a suggested setup for managing your fork, that uses upstream to refer to the original porter repository and origin to refer to your fork:

  1. Fork the repository in GitHub and copy your fork’s reference. click on the green code button on the repository’s homepage, and copy the reference

  2. In a terminal, cd to where you cloned Porter.

  3. Rename the origin remote to upstream:

    git remote rename origin upstream
  4. Add a remote to your local checkout of Porter for your fork:

    git remote add origin
  5. Run git remote -vv to verify that origin is your fork and upstream is the canonical repository.

    $ git remote -vv
    origin (fetch)
    origin (push)
    upstream (fetch)
    upstream (push)
  6. Create a branch for your changes:

    git checkout -b YOURBRANCH main
  7. Push your branch to your fork (origin):

    git push -u origin YOURBRANCH

    Afterwards you can use just git push to synchronize your local branch with your fork on GitHub.

  8. Run git branch -vv to verify that the main branch is synchronized with upstream/main and your branch is synchronized with origin/YOURBRANCH.

    $ git branch -vv
    * mybranch      26d8358f [origin/mybranch] Review feedback
    main            7e120aab [upstream/main]   Bump cnab-go

Configure Signing

Porter requires that all commits are signed. Run the following command to tell git to automatically sign your commits in the Porter repository:

make setup-dco

Build Porter

Now that we have the source code, let’s build porter and make sure everything is working.

make build

You may see a message about your Go bin not being in your PATH. If that happens, Add $GOPATH/bin to your PATH and then run make build again. It should work now but if it doesn’t, please let us know!

Verify Porter

After you have built Porter, the resulting porter command-line tool is placed in the bin directory. Let’s try running porter to make sure everything worked:

./bin/porter help

Now you that you have built Porter, let’s try running a bundle to make sure Docker is installed and configured properly too. This is an abbreviated version of our QuickStart. If you are new to Porter, we recommend trying the QuickStart as well to learn how Porter works.

First let’s do some quick configuration so that you can use the porter executable that you just built, instead of the installed porter. This change isn’t permanent and only affects your current shell session.

export PORTER_HOME=`pwd`/bin
alias porter=`pwd`/bin/porter

Let’s use what you just built and verify that everything is working:

  1. Make a temporary directory for your bundle:
    mkdir -p /tmp/hello-world
    cd /tmp/hello-world
  2. Create a new bundle:
    porter create
  3. Build the bundle:
    porter build
  4. Install the bundle:
    porter install
  5. View your bundle’s status by listing all installed bundles:
    porter list

Change Porter

Let’s make a change to Porter by adding a new command, porter hello --name YOURNAME that prints Hello YOURNAME!.

  1. Create a new file at pkg/porter/hello.go and paste the following content.

    This is the implementation of our new command. We are adding a new function to the Porter struct called Hello that accepts a HelloOptions struct containing the flags and arguments from the command. Our options struct has validation logic so that we can enforce rules such as --name is required.

    package porter
    import (
    // Define flags and arguments for `porter hello`.
    type HelloOptions struct {
       Name string
    // Validate the options passed to `porter hello`.
    func (o HelloOptions) Validate() error {
        if o.Name == "" {
            return errors.New("--name is required")
        return nil
    // Hello contains the implementation for `porter hello`.
    func (p *Porter) Hello(opts HelloOptions) error {
        fmt.Printf("Hello %s!\n", opts.Name)
        return nil
  2. Create a new file at cmd/porter/hello.go and open it in your editor.

    This is the definition of the command. It includes the name of the command, its help text displayed by porter help, defines the flags for the command, and says how to validate and run the command.

    package main
    import (
    func buildHelloCommand(p *porter.Porter) *cobra.Command {
        // Store arguments and flags specified by the user
        opts := porter.HelloOptions{}
        // Define the `porter hello` command
        cmd := &cobra.Command{
            Use:   "hello",
            Short: "Say hello",
            PreRunE: func(cmd *cobra.Command, args []string) error {
                return opts.Validate()
            RunE: func(cmd *cobra.Command, args []string) error {
                return p.Hello(opts)
        // Define the --name flag. Allow using -n too.
        cmd.Flags().StringVarP(&opts.Name, "name", "n", "", "Your name")
        return cmd
  3. Open cmd/porter/main.go and look for the line cmd.AddCommand(buildVersionCommand(p)). Add your new hello command to the supported commands by adding the following line before we add the version command:


Test Your Changes

After you have modified Porter, let’s rebuild and test your changes.

  1. Build porter to incorporate your new command by running make build.
  2. Run ./bin/porter hello --help to see the helptext for your command.
  3. Run ./bin/porter --name YOURNAME to try out your new command.
$ porter hello --name Carolyn
Hello Carolyn!


You can now build Porter, modify its code and try out your changes! 🎉 You are now ready to find an issue and contribute to Porter.