Setup a Go web service as a system-level service on Ubuntu

Stas Smirnov
4 min readJul 13, 2018
“A man running on a dirt path through the grass” by Jenny Hill on Unsplash

In my first article I would like to share with you the knowlegde I gained when was trying to configure a Go web service to work as a system service on Ubuntu. It is not a rocket science, but when I was searching I did not find a complete tutorial for this and thought that it might be useful to consolidate it in one place. So here we go.

I have become interested in Go, so I read a bunch of docs and tutorials and then to get my hands dirty and get a better understanding I decided to develop a simple web service using Go. But then I thought that maybe I can find a project on GitHub that I can contribute to and get some experience at the same time. So I found one, contributed and decided to deploy it somewhere.

Here is an example of a simple Go web service:

package mainimport (
"fmt"
"log"
"net/http"
"os"
)
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello")
}
func main() {
host := os.Getenv("HOST")
port := os.Getenv("PORT")
addr := host + ":" + port
http.HandleFunc("/", handler) log.Fatal(http.ListenAndServe(addr, nil))
}

The application I was using is a bit more complicated, but this example is for those who are not familiar with Go.

Local deployment is not that interesting and is pretty straightforward. For most of my pet projects I use Jelastic. But this time, since it is not a complicated web service with multiple “layers” I decided to use a simple Ubuntu droplet from DigitalOcean. You can find a tutorial of how to set up a droplet here, so I will not stop on this topic.

When I got my droplet, I

  • installed all the neccessary packages, like git to clone the project’s sources, docker to run a DB container and of course go
  • exported GOPATH and set it simply to /opt. GOPATH is an important env variable to specify the location of a workspace in order to work with a project and handle dependencies.
export GOPATH=/opt
export APP_PATH=$GOPATH/src/github.com/<userId>/<projectName>
  • cloned the project’s repo to the APP_PATH
  • installed all project’s dependencies
cd $APP_PATH
go get ./...
  • run my app to make sure everything is ready
env HOST=<hostName> PORT=<port> go run $APP_PATH/app.go
  • and started thinking about how to keep my web service running

First option that came to my mind was just to use the nohup utility. And so it would look like this

env HOST=<hostName> PORT=<port> nohup go run $APP_PATH/app.go &

But then I thought that I need to have my service up and running continuously for some time, and a droplet can be restarted or the app can crash or any other side affect can lead to my web service to stop.

And so a good option is to make my web service a system-level service using systemctl utility. I followed next steps.

  • Created a unit file. A unit file contains configuration directives that describe the unit and define its behavior.
    The /etc/systemd/system/ directory is reserved for unit files created or customized by the system administrator.
sudo nano /etc/systemd/system/unit_name.type_extension
Since I needed a system-level service I used type_extension = service
sudo nano /etc/systemd/system/myservice.service
  • Unit files typically consist of three sections:
    [Unit] — contains generic options that are not dependent on the type of the unit.
    [unit type] — if a unit has type-specific directives, these are grouped under a section named after the unit type.
    [Install] — contains information about unit installation used by systemctl enable and disable commands. In my case the “unit type” is “service”, so the file looks like this
[Unit]
Description=My Always-on Service
[Service]
Type=simple
Restart=always
Environment="GOPATH=/opt"
Environment="APP_PATH=/opt/src/github.com/<userId>/<projectName>"
ExecStart=/usr/bin/env HOST=<hostName> PORT=<port> /usr/bin/go run ${APP_PATH}/app.go
[Install]
WantedBy=multi-user.target

Unit files do not like relative paths, so make sure you use absolute paths to point to the executables.

  • When a unit file was ready I run next two commands
sudo systemctl daemon-reload // always run this command after you created a new unit file or modified an existing unit file
sudo systemctl start myservice.service // to start my service

The expected output is:

- myservice.service - My Always-on Service
Loaded: loaded (/etc/systemd/system/myservice.service; enabled; vendor preset: enabled)
Active: active (running) since <some date> UTC; <age>
Main PID: <pid> (go)
Tasks: <# of tasks> (limit: <limit>)
CGroup: /system.slice/myservice.service
└─3766 /usr/bin/go run /opt/src/github.com/<userId>/<projectName>/app.go

If something goes wrong next output is printed:

- myservice.service - My Always-on Service
Loaded: loaded (/etc/systemd/system/myservice.service; enabled; vendor preset: enabled)
Active: failed (Result: exit-code) since <some date> UTC; <age>
Process: <pid> ExecStart=<the command specified in the unit file>
Main PID: <pid> (code=exited, status=1/FAILURE)

To get more details you can check the Systemd logs using the “journalctl” utility

journalctl -u myservice.service

to follow the log run the same command with “-f” option

journalctl -f -u myservice.service
  • As the service was registered as a systemd service, all standard systemctl commands can be used
sudo systemctl status myservice.service // to check the status
sudo systemctl enable myservice.service // to make the service start automatically on system boot
  • Everythng seemed to be up and running, so I could check one of the endpoints of my service.
    This is based on the sample web service I provided above:
curl -I <hostName>:<port>Output:
HTTP/1.1 200 OK
Date: <date>
Content-Length: 5
Content-Type: text/plain; charset=utf-8

To summarize, in this article I

  • provided an example of a simple Go web service
  • explained how to prepare an environment for running a Go web service cloned from GitHub
  • provided several options to run a Go web service
  • explained how to setup a custom system-level service to run a Go web service

I hope you found this article useful!

--

--