Reinventing The Wheel: Deploying Go Code as Azure Web App or Function App

Soumit Salman Rahman
8 min readJan 26, 2024

--

[This is for you if — you want to reinvent the wheel, you know how to write hello-world code in Go, you know how click some buttons in azure portal and you have heard of the term docker (and not necessarily in the context of docking your boat)]

Growing up I was told that “Failure is the pillar of success”. Fast forward to present day: I tried deploying my Go code as an Azure function app … aaaaand now I am the proud owner of enough pillars to build at least 17 Greek parthenon from scratch. Yup!

It’s not really a news that Azure and Go are not exactly friends. Although Azure is starting to have bit more SDKs and support for Go, Azure App Services DO NOT like Go natively. I have dug through a whole bunch of examples where people tried to work around things, some kinda’ worked, some didn’t. There is also Create a function in Go or Rust using Visual Studio Code — Azure Functions | Microsoft Learn that the lovely people at Azure developer support wrote with utter disregard for making them useful for anyone. If you are trying to do anything past the absolute basic nothing, you are going to want to jump out of a bridge.

  • Azure web app does not natively support go runtime. It’s not a thing.
  • Azure function app — HTTP trigger: Natively supported but the build and deployment are nebulous. Still can’t tell when it will work and when it won’t. You want to use go-gin? Good luck!
  • Azure function app — Time trigger: Not a thing
  • Azure function app — Queue trigger: Not a thing.
  • Azure function app — DB update trigger: Yo! Are you even reading this! NOT a thing!!

But if you are like me and gung-ho on using the combination of Azure App Service and Go, you have some glutton for punishment and I gotchu.

TL;DR — Build a docker image for your Go code and deploy it as a Web App.

“But big daddy I want a function app with time trigger”

“I know! Same $hit. I gotchu”

“But now I have to learn docker .. c’mon cuh”

“It’s just 3 button clicks, 5 lines of code and 4 commands, thaasss it. Even if you copy-paste the codes here it would work for most common cases”

I understand that there are some intrinsic benefits of using serverless compute capability of function app but keep in the mind that function apps come with their own baggage due to the cookie cutter image of the underlying OS and ASP.NET runtime (Perhaps that can be a different can of worms to explore). If you are already at phase of fine tuning your app performance and cost, you will have much better control by using web app over function app anyway. Now here is minimalist approach —

You want a Web App?

  • Write your Go code as you would like a normal human being.
  • I like using Gin-Gonic (github.com) as the web service framework. It saves me a lot of time and code to do most of the common scenarios without having to deal with context management, content type, data type marshalling and unmarshalling, and whole other work for work.
  • Create a docker image.
  • Deploy it as Azure Web App. You are done.

You want a Function App?

  • Still do what you would for a web app.
  • Then use Azure Logic app to create a no-code triggers like time trigger, message queue triggers with no-code.
  • Function apps are inherently HTTP calls. The other triggers are just wrappers underneath.

Step 1: Write you go app like a regular human being

  • You can use http.Response / http.Request standard Go library like the Microsoft Learn example (and shoot yourself through the process) or you can use go-gin library. I personally really like using go-gin because I can simply focus on the app’s functionality without having to write a whole bunch of code for fancy parsing of input and output. Sample soumitsalman/go-gin-container-working (github.com)
gowebapp/
├── shared/
│ ├── processor_lib1.go
│ └── processor_lib2.go
├── dockerfile
├── go.mod
├── go.sum
└── main.go
package main

import (
"log"
"net/http"

"examples.com/gowebapp/shared"
"github.com/gin-gonic/gin"
)

func handlePing(ctx *gin.Context) {
// sending response
ctx.JSON(http.StatusOK, shared.GetPong())
}

func handleContents(ctx *gin.Context) {
// sending response
ctx.JSON(http.StatusOK, shared.GetContents())
}

func main() {
log.Println("Starting app")
router := gin.Default()

router.GET("/ping", handlePing)
router.GET("/contents", handleContents)

router.Run()
}

“But the port though…”

“Don’t worry about it.”

  • The runtime commonly sets it at 8080. If you want to change you can pass in an environment variable and read it like a regular human being: go.Getenv(‘PORT’). And this also applies to anything that you want to pass in as a custom environment variable.
  • Run it in your local machine and see if that works. I use Postman for testing or inspecting REST API. You can use zaproxy, fiddler, your browser or whatever else you like.

Step 2: Create your docker image

  • Create an account: Go to docker.com and create an account (if you don’t have one already). Create a Personal Access Token (PAT)
  • Install docker execution engine on your dev machine. Follow the instructions here Overview of Docker Desktop | Docker Docs (unlike Azure learning resource, this one is actually helpful). I use Zorin 17 as my dev OS and I ran the following.
sudo apt install -y docker-ce docker-ce-cli containerd.io
sudo systemctl start docker
sudo systemctl enable docker
  • Create dockerfile: create a file named dockerfile in your code directory (don’t put random a$$ name — at least not for now). Write the following code. Note that I am using alpine Linux with built-in Go runtime. You can use other base images from here. I use alpine because it is minimalist distro without bloating. This makes my deployment way faster (reduced from 7 mins → less than a minute) and compute speed much faster.
FROM golang:1.22-rc-alpine
# alpine is not necessary. it can be 1.20 or other tags
# set a working directory to function from. It doesn't have to be /app
WORKDIR /app
# technically you dont need to copy all the files. ONLY the go stuff. But I was being lazy
COPY . .
# this downloads all the dependencies that you have in the code
RUN go get
# or
# RUN go mod download
# create the binary/executable. The name does not have to bin e.g. you can name it mylittlebunny
RUN go build -o bin .
# tell docker which binary is your application
ENTRYPOINT [ "/app/bin" ]
  • Build:
sudo docker build . -t soumitsr/go-gin-container:latest 
# (<your-docker-hub-username>/<your-container-image-name>:<version> . Put whatever name you want for the image. It does NOT have to be the name of the parent directory.
  • Run and Test:
sudo docker run -p 8080:8080 soumitsr/go-gin-container:latest
# always pass -p for port forwarding.
# Your code is now running in a container.
# Trying to test directly from your dev machine's browser (which is the host),
# will NOT find the port in the container without port forwarding.
  • If you have environment variables that you need to read (e.g. PORT)
docker run -e PORT=9000 -p 9000:9000 soumitsr/go-gin-container:latest
# use parameter -e . usually good for one variable

docker run --env-file=/path/to/file.env soumitsr/go-gin-container:latest
# use parameter --env-file .good for a whole list of variables
  • If you are looking for inspiration, here is how my .env file looks like
MY_PASSWORD=123456 
# best password ever
MY_DADDYS_PASSWORD=password
MY_FERRETS_SOCIAL_SECURITY_NUMBER=123–45–6789
  • Test the same way you tested Step 1

“But big daddy — why have all these passwords as environment variables when I can just hard code them?”

“Sure, you can suga’, go right ahead. And then … check-in that code in github, in a public repo and post it on LinkedIn and Facebook so everyone can see all the pretty thangs you has. Also, this way in future with your newfound wisdom if you decide to remove them from source code, they will still stay in the history FOR EVRRRRR”

sudo docker login 
# use your username and PAT when prompted
sudo docker push soumitsr/go-gin-container:latest

Step 3: Create your Azure web app and deploy

  • Create new Azure app service
  • Select Docker Container as Publish value
Create Azure Web App with Docker Container as Publish
  • Set image to the same name of the image that you published. You can set the continuous deployment later
Assign which docker image should be pulled in
  • Set the environment variables through Configuration
Add your environment variables.

Step 4: Create Logic app to trigger like Function app

If you want a non-HTTP trigger (e.g. time trigger) for your web app, you can create an Azure Logic app with time trigger. You can do similar stuff with message queue and DB update trigger. Here is a sample and believe it or not this is not trash (instead of Standard/Bing Maps just to Custom and pass in the URLs for your web app).

Et Voila! Now you have a live web app / REST API written in Go and run as azure app service …

  • Which is wide open to the internet for EVERYONE to bang against
  • Has built in no rate limiting.
  • No authentication or authorization.
  • Highly questionable credential and secret management (well … there is no question, really. It’s bad)

And basically, NO security … but who needs ’em anyway. I could sit here and write more on this, but my hot coco is ready (and no that’s not a metaphor). ✌️✌️

Some existing resources

PS

#go #azure #appservice #webapp #functionapp #logicapp #timetrigger #docker #webapi #softwaredevelopment

--

--