Simple deployment with Systemd and Go
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.