Deploying your Golang App without Docker

How to deploy an app written in Go? That’s one of the questions I had when I first wanted to get something online after playing with Go for a while. Thinking back I think the first project I got online, was probably the redirect checker which is explained in detail here: Tracing or Preventing HTTP Redirects in Golang

When you just want to get stuff running, you probably don’t want to learn a container system, but just get it out there which is exactly what we’re going to have a look at in this post.

Note: this post does not describe best practices, it is supposed to lower the barrier of entry of showing your friends what you’ve built with Go without having to learn a service like Hyper, Heroku or a system like Docker.

This post is another in the series of how we made photographerexcuses.com!

Setting up a Linux VPS

For me one of the most straight-forward ways of deploying any web application is to set up a virtual private server somewhere. They’re just like my computer, except they’re always on. I’ve written a quick comparison of cheap VPS providers if you’re in doubt which one to try.

Next we need a directory on the server and a user that can access it, if in doubt, you can use your users home directory:

mkdir project_name

Getting your Binary “up there”

If you have you ssh key setup, you can simply use Rsync to move files from your box to your remote server:

#!/bin/bash
go build main.go
rsync -avz -e ssh main user@SERVER_IP:/home/jonathan/project_name

Now when we connect to our VPS, we can change to the directory and execute our binary and it runs (if compiled for the same architecture).

ssh user@remote_ip
cd project_name
./main

Quick and Dirty deploy with screen

The only problem with that is, that as soon as we close our terminal, the execution will be halted. To stop that from happening, we can use screen. If screen is not installed on your server, you can easily add it through apt or equivalents:

sudo apt-get install screen

Now we can start a new screen session by typing screen. If we now repeat

cd project_name
./main

followed by pressing CTRL+A+D we will detach that screen session. If we now run screen -ls we will see all detached screen sessions.

user@tempest:~# screen -ls
There are screens on:
    2851.pts-0.tempest    (04/01/2017 04:01:56 PM)    (Detached)
    2459.pts-0.tempest    (04/01/2017 03:56:38 PM)    (Detached)

Running pstree will illustrate the concept of screen a bit better

user@tempest:~# pstree
systemd─┬─accounts-daemon─┬─{gdbus}
        │                 └─{gmain}
        ├─acpid
        ├─agetty
        ├─atd
        ├─cron
        ├─screen───bash
        ├─screen───bash───top
        ├─snapd───5*[{snapd}]
        ├─sshd───sshd───bash───pstree

Note that one of my screen sessions isn’t doing anything apart from being logged in to a shell, the other one is running top.

You can run as many screen sessions as you like and use screen -r to re-attach it (jump back into it). If you have multiple detached sessions, you’ll have to specify which screen you want to reconnect to like screen -r 2851.pts-0.tempest.

Great! Now we know how to keep our Go App running when we terminate our SSH connection, the problem is that if our VPS should restart for any reason, the screen sessions will die and not come back up automatically after a reboot, if you want to ensure such behavior, just read on.

Creating an Init Script for your Go App

cd /etc/init.d/
touch my-service.sh
vim my-service.sh
chmod +x my-service.sh
#!/bin/sh
### BEGIN INIT INFO
# Provides:          main_go
# Required-Start:    $network $syslog
# Required-Stop:     $network $syslog
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: Start go server at boot time
# Description:       your cool web app
### END INIT INFO


case "$1" in
  start)
    exec /var/www/project_name/main &
    ;;
  stop)
    kill $(lsof -t -i:4000)
    ;;
  *)
    echo $"Usage: $0 {start|stop}"
    exit 1
esac
exit 0

Let’s test if that works:

root@tempest:/etc/init.d# ./my-service.sh start
server running

If you then, while this is running open another ssh connection to the same server and run:

cd /etc/init.d
./my-service.sh stop

it will append Terminated to your first ssh session. We have successfully confirmed both start and stop work.

Now let’s start the service for good, without having an active shell listening to the output:

/etc/init.d/my-service.sh start

To enable your service on boot you need to execute the following:

update-rc.d my-service.sh defaults

You can check if the correct symlinks to the different run-levels were created by running ls -r /etc/rc* | grep my-service

My output:

K01my-service.sh
S01my-service.sh
S01my-service.sh
S01my-service.sh
S01my-service.sh
K01my-service.sh
K01my-service.sh

To test that, use: init 6 and wait for your server to come back online. Don’t do this if you are running other things on that server that need to be up.

Did this post help? Do the examples work for you? Let me know in the comments!

1 thought on “Deploying your Golang App without Docker”

  1. systemd? so much easiery:

    Create /lib/systemd/system/my-service.service
    “`
    [Unit]
    Description=my service

    [Service]
    Type=simple
    ExecStart=/opt/go/bin/my-service
    Restart=always
    RestartSec=5s

    [Install]
    WantedBy=multi-user.target
    “`
    start with `systemctl start my-service.service`
    autostart on reboot with `systemctl enable my-service.service`

    if you are lazy and has a lot of go applications make a abstract service.

    Create /lib/systemd/system/go@.service
    “`
    [Unit]
    Description=the golang binary %i

    [Service]
    Type=simple
    ExecStart=/opt/go/bin/%i
    Restart=always
    RestartSec=5s

    [Install]
    WantedBy=multi-user.target
    “`
    start with `systemctl start go@my-service.service`
    autostart on reboot with `systemctl enable go@my-service.service`

Leave a Reply

Your email address will not be published. Required fields are marked *