An introduction to the Zerops build & deploy pipeline: A Golang example
Tags: cloud, cloudnative, go
Published: 2022-11-07 16:00:44

Welcome back to our article series about __Golang in Zerops__! In case you missed the previous post, have a look at [Building a Simple TODO App with Gin-gonic in Zerops: A step-by-step Guide](https://dev.to/reoops/building-a-simple-todo-app-with-gin-gonic-in-zerops-a-step-by-step-guide-5348) to catch up on the basics.

In this post we will focus on _building and deploying_ your own application in [Zerops](https://contabo.com/en/zerops/). All you need to do is add a [`zerops.yml`](https://docs.contabozerops.com/documentation/build/build-config.html#introduction) configuration file to the root directory of your app. We will go in detail through the file so that it works best for your application.

## 1. Set up your project in Zerops

> Feel free to skip this section if you are already familiar with Zerops.

Complete these steps to have Zerops ready for your application:

1. Login to Zerops
2. Create a new [project](https://docs.contabozerops.com/documentation/overview/projects-and-services-structure.html#project)
3. Add a new Golang [service](https://docs.contabozerops.com/documentation/overview/projects-and-services-structure.html#services-containers)

Note down the names of the project and the service, you will need them for future reference. For our example application we created a project called _myProject_ and a Golang service called _helloworld_.

### 1.1. Pipeline triggering (GUI/zCLI)

Once you have your `zerops.yml` ready, there are two options to trigger the build & deploy pipeline:

#### Git integration + Zerops GUI

Recommended when your app source code is already versioned in a _git_ repository. In the detail of your Go service, go to _Build_, _deploy_, _run pipeline settings_, connect with either a Github or Gitlab repository and _Activate pipeline trigger_ according to your needs.

#### zCLI

If command line is your best friend or it just better suits your use case, you can:

1. Install [zCLI](https://docs.contabozerops.com/documentation/cli/installation.html)
2. Login to zCLI with an [access token](https://docs.contabozerops.com/documentation/cli/authorization.html#login-using-personal-access-token) generated in GUI
3. Run a `zcli push <project_name> <service_name>` [command](https://docs.contabozerops.com/documentation/cli/available-commands.html#push-project-name-or-project-id-service-name-flags) (in our case it is `zcli push myProject helloworld`)

Nevertheless, once the pipeline is triggered, you can follow its progress in the Zerops GUI with more information provided.

### 1.2. Example application

The most simple example was chosen to eliminate the need to test the app locally before trying out Zerops.

> You are more than welcome to build, deploy and run __your own__ Go application in Zerops while following this example only as a guide.

Our example app is a slight modification of the famous “Hello, World!” program. There is only one file, `main.go`. The `main` function first sets up the `getHelloWorld` handler function to the `/hello-world` path, and then it starts the http server with the gin-gonic library. The `getHelloWorld` function only prints “Hello, World!”. The server is listening on port `8080`, which is the default [port](https://docs.contabozerops.com/documentation/routing/routing-between-project-services.html) created for Go services in Zerops.

```go
package main

import (
  "fmt"
  "log"
  "net/http" "github.com/gin-gonic/gin"
)

func main() {
  fmt.Println("Running http server")

  router := gin.Default()
  router.GET("/hello-world", getHelloWorld)
  log.Fatal(router.Run(":8080"))
}

func getHelloWorld(c *gin.Context) {
  fmt.Println("received /hello-world request\n")
  c.String(http.StatusOK,"Hello, World!")
}
```

We also need to initialize a _go module_, which creates `go.mod` and `go.sum` files. You can either do it yourself or copy the contents from our repository. The project structure is as follows:

```txt
myProject
├── go.mod
├── go.sum
├── zerops.yml
└── main.go
```

## 2. Build & deploy pipeline explained

First we will illustrate the process and then explain the key words when editing the `zerops.yml` file. It all starts with an image. It is either created by combining a `base` image with `prepare` commands, or loaded from the cache from previous builds if nothing has changed (which contains your `cached` files as well). The image is then run inside a "build" container. After that, all `build` commands are executed and a deployment artifact is created. It is 'deployed' to another 'runtime' container, where it is run.

Now let’s have a look at the `build` section of the `zerops.yml` file where you can customize the process above for your Go services. Note that the service name is the root key in the `zerops.yml` file.

```yaml
helloworld:
  build:
    base: # optional
    prepare: # optional
    build: # required
    deploy: # required
    cache: # optional
run:
  ...
```

To build your Go application, you definitely need to run the `go build ...` command. That’s what the __required build__ section is for! The following command compiles our package with a single `main.go` source file into an executable called `app`.

```yaml
...
  build:
    - go build -o app main.go
...
```

And that’s it? Well, at the moment the command would fail as we have not installed `go` itself and set the environment. To __save you the trouble__ of finding the right package and going through the installation process, Zerops has created several __base__ images with the most common dependencies preinstalled, such as _go_ and _git_ in `go@1` base image:

```yaml
...
  base: [go@1]
  build:
    - go build -o app main.go
...
```

In case you need to use an older version of go or want to customize the dependencies in any way, use the [__prepare__](https://docs.contabozerops.com/documentation/build/build-config.html#prepare-optional) section. It is designed for any commands you might need to execute to successfully run the build commands. For instance, have you got `git` or `wget` in mind? No problem, here is an example:

```yaml
...
    prepare:
      -|
        add-apt-repository ppa:longsleep/golang-backports
        apt update
        apt install -y golang-1.17 git wget
        ln -s /usr/lib/go-1.17/bin/go /usr/bin/go
        ln -s go-1.17 /usr/lib/go
    build:
      - go build -o app main.go
...
```

Another __required__ key in the _build_ section of `zerops.yml` file is [__deploy__](https://docs.contabozerops.com/documentation/build/build-config.html#deploy). You need to let Zerops know which files or folders need to be deployed to a container where your application will be running. In our case we only need the `app` executable. The value is an array of paths allowing you to deploy single files or entire folders, see the [documentation](https://docs.contabozerops.com/documentation/build/build-config.html#deploy) for all supported formats.

```yaml
...
  build:
    base: [ go@1 ]
    build:
      - go build -o app main.go
    deploy: [ app ]
...
```

The last part of the build section is an optional [__cache__](https://docs.contabozerops.com/documentation/build/build-config.html#cache-optional). After every build the resulting image of base image and prepare commands is cached and used next time if nothing changes in these sections of the `zerops.yml`. However all files in your project root directory are discarded, unless you explicitly tell Zerops to cache them too. In the Go service there is not much to cache that would significantly speed up the build process compared to the `node_modules` folder in a _Node.js_ service, for example. However let’s see how we can cache go dependencies as well - __but take it only as an example!__

It is possible to cache any files and folders in the project root directory except the `.git` folder. Since the default folder where the go modules are stored is `~/go/pkg/mod`, we need to change it to e.g. `$(pwd)/mod` (_working directory is by default the root of your project_), and then we can cache this folder. The expected format is the same as for deploy.

```yaml
...
  build:
    base: [ go@1 ]
    build:
      - go env -w GOMODCACHE=$(pwd)/mod
      - go build -o app main.go
    deploy: [ app ]
    cache: [ mod ]
...
```

To check that the cache is applied, you should see in the logs that the dependencies are only downloaded during the first build.

![Empty build cache](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cc0tex0i3w3t4jtrq216.png)

![Build using cache](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/97idsoxr5x1e8n6kc2be.png)

Now we are done! The last thing is to [__run__](https://docs.contabozerops.com/documentation/build/build-config.html#run-part-and-its-properties) your deployed code. The only required key for Go services is `start`, its value is expected to be a command that starts your application, such as `./app` runs the `app` executable in our example.

The full working `zerops.yml` file to build, deploy and run our helloworld application is as follows:

```yaml
helloworld:
  build:
    base: [ go@1 ]
    build:
      - go build -o app main.go deploy: [ app ]
    run:
      start: ./app
```

To make sure the application is up and running, open the [_Runtime log_](https://docs.contabozerops.com/documentation/zerops-logs/runtime-logs.html) section in the Golang service detail.


![Runtime log](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/chtv97vlfsbq3q79slow.png)

If you have followed our example, or you have your own http server running, you can also test it by [_enabling a subdomain_](https://docs.contabozerops.com/documentation/routing/using-your-domain.html) (_for development purposes_) or [_setting up your domain_](https://docs.contabozerops.com/documentation/routing/using-your-domain.html). Either way, when the `/hello-world` request is made, you should see “Hello, World!” printed.


![Output in Browser](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eymgrqab4dk3huioict8.png)


![Logging](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g0abs93l37wurnf6cx31.png)

Easy, right? :) We are well aware that the real-life application will be more complex. If you have any issues, join our [Discord server](https://discord.gg/SJ95zkGuS7) to ask any questions and/or share your solutions with others. Also check our [documentation](https://docs.contabozerops.com/documentation/) to learn more about Zerops.

__We are looking forward to seeing your own pipelines running!__


## Conclusion (tl;dr)

* __always__ specify `build` command
* use predefined `base` images or specify custom `prepare` commands to build your app - __or BOTH!__
* __always__ specify which files you want to `deploy` to a runtime container
* you can __optionally__ `cache` any deployed files to speed up future builds