Simple deployment with Systemd and Go

Simple deployment with Systemd and Go

Posted

If you want a simple way of deploying your go application without having to ssh into your server, do git pull and running all the commands manually, keep reading.

This post assumes that you have a server running somewhere with ssh enabled. Linode and Digital Ocean are quite popular choices I think. My server is currently running with Ubuntu, but that should not matter much as long as the distro of your choice is using systemd.

Building the go application

Let’s build a simple sample application that just fires up a http server with a single endpoint.

package main

import (
	"log"
	"net/http"
)

func main() {
	mux := http.NewServeMux()
	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("hello world"))
	})
	log.Println("Listening at port :1234")
	http.ListenAndServe(":1234", mux)
}

Now we need to build our application for our server environment. I like using a Makefile to structure these things, this is optional.

We need a binary that we can run our linux server environment. We can set different environment variables to tell go to build for another system. As I am running linux ubuntu, this is how I do it:

server-build:
	export GO111MODULE="on"; \
	go mod download; \
	go mod vendor; \
	CGO_ENABLED=0 \
	GOOS=linux \
	GOARCH=amd64 \
	go build -o bin/server

Now we can deploy this binary to our server with rsync. You can read more about rsync here. Another option to rsync is scp. You can also use something like filezilla, but we want too stay in our terminal so that is not really a option here.

deploy:
	rsync -avz bin/server  your_nickname@your_server:/web-server/

Now our binary is in the /web-server directory on our server, this path is optional but it is important that your user has read and write access to that directory.

The -a flag puts rsync in archive mode.  It synchronizes the directories recursively. It also keeps the ownership of users and groups, permissions, symbolic links (symlinks), and timestamps -v is just to tell us everything that us going and -z is compressing the files.

Now our Makefile should look something like this:

server-build:
	export GO111MODULE="on"; \
	go mod download; \
	go mod vendor; \
	CGO_ENABLED=0 \
	GOOS=linux \
	GOARCH=amd64 \
	go build -o bin/server

deploy:
	rsync -avz bin/server  your_nickname@your_server:/web-server/

Now we need to run that binary on our server.

Setting up systemd

Note: All the following commands will be run on your server:

ssh username@server.com

Creating our systemd configuration file

To make our deployment smooth as butter, we need to create our configuration with our current user. That way we can run our systemctl commands without sudo.

We start with creating our configuration file

systemctl edit --user --force --full go-webserver

This will open the file in your default server editor, this will probably be something like nano or vi. It’s worth noting that the --force flag looks a bit strange here, but without it systemctl will complain and tell you to add it, so here we are.

Add this content and then save:

[Unit]
Description=our go web server
ConditionPathExists=/go-webserver
After=network.target

[Service]
Type=simple
LimitNOFILE=1024

Restart=on-failure
RestartSec=10
StartLimitInterval=60

WorkingDirectory=/go-webserver
ExecStart=/go-webserver/bin/app --name=gowebserver

[Install]
WantedBy=default.target

This will create /home/your-user/.config/systemd/user/go-webserver.service

Now we need to enable our new systemd service. Enable means that systemd will start this service when the server restarts.

systemd --user enable go-webserver && \
systemd --user start go-webserver && \
systemd --user status go-webserver

With the last command we should see something like “Listening at port :1234”. The last thing we have to do is to prevent our service from closing after we log out from our web server. This is the default behaviour of systemd. To overwrite this open up the systemd logind.conf file like this:

sudo nano /etc/systemd/logind.conf

Add this line to the end of that file, close it and restart your server to make the change take effect.

UserStopDelaySec=infinity

Restart your server with sudo reboot. Note, this will close your ssh connection to the server (duh).

Now we need to expose our new service to the world.

Web server

After rebooting, ssh back into your server. You should check the service status again to make sure it is running after the reboot.

If you already have a web server running, all you have to do is to set up a reverse-proxy for localhost on port 1234.

If you don’t have a web server I really recommend caddy

Look at the documentation here on how to set up caddy on your server. After that open your Caddyfile and add this snippet to set up our reverse-proxy:

server.com {
        reverse_proxy {
                to localhost:1234
        }
}

After that run sudo systemctl restart caddy.service. Now you should be able to see hello world on your server.com url. You can now exit the your server.

Everything is now ready for our easy deployment.

Deploying our application

So if everything is correct from our previous steps, all we have add to our Makefile is this.

restart-service:
	ssh yourname@server.com systemctl --user restart go-webservice.service

Now when you run make restart-server, our system service is restarted.

Let’s make a change to our main.go file, save and deploy.

w.Write([]byte("hello world!!!"))

Save the file and run our commands:

make server-build
make deploy
make restart-service

Now, if you visit yourserver.com you should see our hello world!!! update.

That’s it, now you can build and deploy blazingly fast. Thanks for reading and I hope you liked it.