github.com/goreleaser/goreleaser@v1.25.1/www/docs/blog/posts/2023-01-10-azure-devops.md (about) 1 --- 2 date: 2023-01-10 3 slug: azure-devops 4 categories: 5 - tutorials 6 authors: 7 - dirien 8 --- 9 10 # Releasing multi-platform container images with GoReleaser in Azure DevOps 11 12 <!-- more --> 13 14 ## Introduction 15 16 In this article, we learn how to use [GoReleaser](https://goreleaser.com/) to build and release a multi-platform container image to [Azure Container Registry](https://azure.microsoft.com/en-us/services/container-registry/) in [Azure DevOps](https://azure.microsoft.com/en-us/services/devops/). 17 18 This is particularly interesting for teams, who are using mainly Azure and Azure DevOps for their projects and want to build and release container images to Azure Container Registry. 19 20 I try to follow the great article on how to create multi-platform container 21 images using GitHub actions written by Carlos, the core maintainer of 22 GoReleaser. If you had no chance to read his blog, [here](https://carlosbecker.com/posts/multi-platform-docker-images-goreleaser-gh-actions/) is the link to it. 23 24 Before we start, let’s take a look on the prerequisites. 25 26 ## Prerequisites 27 28 - [Azure DevOps](https://azure.microsoft.com/en-us/services/devops/) account. 29 - [The GoReleaser Azure DevOps Extension](https://marketplace.visualstudio.com/items?itemName=GoReleaser.goreleaser) installed. 30 - [Azure](https://azure.microsoft.com/en-us/) account. 31 - [GoReleaser](https://goreleaser.com/install/) installed on your local machine. 32 33 ## The sample application 34 35 Before we can start to set up our pipeline and infrastructure components, lets have a look at the sample application we are going to use in this demo. To keep things simple, I created basic Hello World server using mux library from the Gorilla Web Toolkit. 36 37 Add the library to the `go.mod` file: 38 39 ```gomod 40 module dev.azure.com/goreleaser-container-example 41 42 go 1.19 43 44 require github.com/gorilla/mux v1.8.0 45 ``` 46 47 After adding the library, we can move over to implement the basic logic of the application. The server should return a Hello World! string, when we curl the root path of the server. 48 49 In mux this is done, with adding a new route to the router and adding a handler function to it. In my case called HelloWorldHandler. 50 51 Then we can start the server and listen on port 8080. 52 53 ```go 54 package main 55 56 import ( 57 "log" 58 "net/http" 59 "os" 60 "strings" 61 62 "github.com/gorilla/mux" 63 ) 64 65 const ( 66 // Port is the port the server will listen on 67 Port = "8080" 68 ) 69 70 func HelloWorldHandler(w http.ResponseWriter, r *http.Request) { 71 w.Write([]byte("Hello World!")) 72 } 73 74 func main() { 75 r := mux.NewRouter() 76 r.HandleFunc("/", HelloWorldHandler) 77 port := os.Getenv("PORT") 78 if port == "" { 79 port = Port 80 } 81 for _, env := range os.Environ() { 82 if strings.HasPrefix(env, "TEST") { 83 log.Printf("%s", env) 84 } 85 } 86 log.Println("Listening on port " + port) 87 log.Fatal(http.ListenAndServe(":"+port, r)) 88 } 89 ``` 90 91 As we want to create a container image, we need to add a Dockerfile. GoReleaser will then build our container image by copying the previously built binary into the container image. Remember: We don't want to rebuild the binary. So no multi-stage Dockerfile. This way we are sure, that the same binary is used for all distribution methods GoReleaser is offering, and we intended to use. 92 93 ```Dockerfile 94 # Dockerfile 95 FROM cgr.dev/chainguard/static@sha256:bddbb08d380457157e9b12b8d0985c45ac1072a1f901783a4b7c852e494967d8 96 COPY goreleaser-container-example \ 97 /usr/bin/goreleaser-container-example 98 ENTRYPOINT ["/usr/bin/goreleaser-container-example"] 99 ``` 100 101  102 103 You may spot that I use a static container image as base image from Chainguard. Chainguard images are designed for minimalism and security in mind. Many of the images provided by Chainguard are distroless, which means they do not contain a package manager or any other programs that are not required for the specific purpose of the image. Chainguard images are also scanned for vulnerabilities and are regularly updated. You can find more information about Chainguard images here: 104 [**Chainguard Images** 105 *Chainguard Images are security-first container base images that are secure by default, signed by Sigstore, and include…*www.chainguard.dev](https://www.chainguard.dev/chainguard-images) 106 107 Let’s pause a minute here and test that everything is working as expected. We can test the application by running it locally: 108 109 ```bash 110 GOOS=linux GOARCH=amd64 go build -o goreleaser-container-example . 111 docker buildx build --platform linux/amd64 -t goreleaser-container-example . 112 docker run -p 8080:8080 goreleaser-container-example 113 ``` 114 115 After spinning up the container, you should see the following output: 116 117 ```bash 118 2023/01/10 10:49:31 Listening on port 8080 119 ``` 120 121 And if we run a curl command in another terminal, we should see the following output: 122 123 ```bash 124 curl localhost:8080 125 Hello World! 126 ``` 127 128 Perfect! Everything works as we expected it. Now we can start working on the GoReleaser parts. 129 130 ## GoReleaser config file 131 132 We need to provide a goreleaser.yaml config file in the root of our project to tell GoReleaser what to do during the release process. In our case to let GoReleaser to build our container image. To create the goreleaser.yaml we can run following command: 133 134 ```bash 135 goreleaser init 136 ``` 137 138 This should generate the config file for us: 139 140 ```bash 141 • Generating .goreleaser.yaml file 142 • config created; please edit accordingly to your needs file=.goreleaser.yaml 143 ``` 144 145 The good part when using the init command is, that the goreleaser.yaml comes with some default values. We need to change content as we not need everything GoReleaser is doing by default. Here is the content of the goreleaser.yaml for this demo: 146 147 ```yaml 148 # This is an example .goreleaser.yml file with some sensible defaults. 149 # Make sure to check the documentation at https://goreleaser.com 150 before: 151 hooks: 152 # You may remove this if you don't use go modules. 153 - go mod tidy 154 builds: 155 - env: 156 - CGO_ENABLED=0 157 goos: 158 - linux 159 - darwin 160 ``` 161 162 Later we add the part needed to create the multi-platform container images but for now we dry-run the release process with following GoReleaser command: 163 164 ```bash 165 goreleaser release --snapshot --rm-dist 166 ``` 167 168 Next to the logs of GoReleaser release process, you should also have a dist folder with all the binaries in it. 169 170 > _Exclude this folder in your .gitignore file, to prevent accidentally committing the binaries to your repository._ 171 172 ## Azure Container Registry 173 174 > _If you already have an Azure Container Registry you can skip the parts of the creation of the Azure Container Registry._ 175 176 There are several ways, you can create a container registry: You can use the Azure Portal, the Azure CLI, the Azure PowerShell or your favorite Infrastructure as Code tool of choice. 177 178 In this demo, I will use the Azure CLI to create the container registry. You can 179 find more information about the Azure CLI 180 [here](https://docs.microsoft.com/en-us/cli/azure/?view=azure-cli-latest). 181 182 First log into the Azure account with the Azure CLI: 183 184 ```bash 185 az login 186 ``` 187 188 We then need to create the resource group and then the container registry service with following commands: 189 190 ```bash 191 # create a resource group in WestEurope datacenter 192 az group create --name goreleaser-rg --location westeurope 193 # create the Azure Container registry 194 az acr create --resource-group goreleaser-rg --name mygoreleaserregistry --sku Basic 195 ``` 196 197 When the container registry is up and running, we can add the dockers configuration to our goreleaser.yaml. But we need to name of our container registry beforehand. 198 199 Use following command to retrieve the name: 200 201 ```bash 202 az acr show --resource-group goreleaser-rg --name mygoreleaserregistry --query loginServer --output tsv 203 ``` 204 205 This is the new part we need to add to our `goreleaser.yaml,` to activate the build of the container image and manifest. 206 207 If you want to know more about the manifest files, I wrote an article about it 208 [here](/blog/docker-manifests). 209 210 ```yaml 211 --- 212 dockers: 213 - image_templates: 214 [ 215 "mygoreleaserregistry.azurecr.io/{{ .ProjectName }}:{{ .Version }}-amd64", 216 ] 217 goarch: amd64 218 dockerfile: Dockerfile 219 use: buildx 220 build_flag_templates: 221 - --platform=linux/amd64 222 - image_templates: 223 [ 224 "mygoreleaserregistry.azurecr.io/{{ .ProjectName }}:{{ .Version }}-arm64", 225 ] 226 goarch: arm64 227 dockerfile: Dockerfile 228 use: buildx 229 build_flag_templates: 230 - --platform=linux/arm64/v8 231 docker_manifests: 232 - name_template: "mygoreleaserregistry.azurecr.io/{{ .ProjectName }}:{{ .Version }}" 233 image_templates: 234 - "mygoreleaserregistry.azurecr.io/{{ .ProjectName }}:{{ .Version }}-amd64" 235 - "mygoreleaserregistry.azurecr.io/{{ .ProjectName }}:{{ .Version }}-arm64" 236 - name_template: "mygoreleaserregistry.azurecr.io/{{ .ProjectName }}:latest" 237 image_templates: 238 - "mygoreleaserregistry.azurecr.io/{{ .ProjectName }}:{{ .Version }}-amd64" 239 - "mygoreleaserregistry.azurecr.io/{{ .ProjectName }}:{{ .Version }}-arm64" 240 ``` 241 242 ## Azure DevOps 243 244 With the infrastructure done and GoReleaser config finished, we can set up Azure DevOps Service. 245 246 _Switch to the Service Connections screen_ 247 248 _Click on the New service connection button_ 249 250 _Select Azure Container Registry and connect your Azure subscription to it_ 251 252 Time for the last part of our demo: Setting up the Azure DevOps pipeline. I will not go too much into detail about the pipeline, as this is not the focus of this demo. But I will show you the important parts of the pipeline. 253 254 First notable part is the multi-platform command task. I simply followed the instructions from this [article](https://learn.microsoft.com/en-us/azure/devops/pipelines/ecosystems/containers/build-image?view=azure-devops#how-do-i-build-linux-container-images-for-architectures-other-than-x64) on how to setup the task to build multiarch images. 255 256 Next section in the pipeline is the GoReleaser task. This task is using the 257 GoReleaser extension from the Azure DevOps Marketplace. You can find more 258 information about the extension [here](https://marketplace.visualstudio.com/items?itemName=ms-azuretools.goreleaser-task). 259 260 I just added the args field and set the value to release `--rm-dist` and defined a condition to only run the task on a tag as GoReleaser will not release on a "dirty" git state. 261 262 This is the complete pipeline: 263 264 ```yaml 265 trigger: 266 branches: 267 include: 268 - main 269 - refs/tags/* 270 variables: 271 GO_VERSION: "1.19.4" 272 DOCKER_BUILDKIT: 1 273 pool: 274 vmImage: ubuntu-latest 275 jobs: 276 - job: Release 277 steps: 278 - task: GoTool@0 279 inputs: 280 version: "$(GO_VERSION)" 281 displayName: Install Go 282 - task: Docker@2 283 inputs: 284 containerRegistry: "goreleaser" 285 command: "login" 286 addPipelineData: false 287 addBaseImageData: false 288 - task: CmdLine@2 289 displayName: "Install multiarch/qemu-user-static" 290 inputs: 291 script: | 292 docker run --rm --privileged multiarch/qemu-user-static --reset -p yes 293 - task: goreleaser@0 294 condition: and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/')) 295 inputs: 296 version: "latest" 297 distribution: "goreleaser" 298 workdir: "$(Build.SourcesDirectory)" 299 args: "release --rm-dist" 300 ``` 301 302 To run a release, you need to create a tag in Azure to get the release process started. 303 304 _Logs produced during the release process_ 305 306 And you should see in the Repository tab of your Azure Container Registry service in the Azure Portal UI the multi-platform container images. 307 308 _List of all produced multi-platform container images_ 309 310 ## Conclusion 311 312 In this demo, I showed you how to create a multi-platform container image using GoReleaser and Azure DevOps and store this image in Azure Container Registry for further usage in your Container based Azure services. 313 314 Setting up all the parts was pretty straight forward where GoReleaser is doing the heavy lifting for us. 315 316 Go ahead and give it a try and let me know what you think about it.