Golang production web application configuration
For those of you running Go backends in production:
What is your stack / configuration for running a Go web application?
I haven't seen much on this topic besides people using the standard library net/http package to keep a server running. I read using Nginx to pass requests to a Go server - nginx with Go
This seems a little fragile to me. For instance, the server would not automatically restart if the machine was restarted (without additional configuration scripts).
Is there a more solid production setup?
An aside about my intent - I'm planning out a Go powered REST backend server for my next project and want to make sure Go is going to be viable for launching the project live before I invest too much into it.
Go programs can listen on port 80 and serve HTTP requests directly. Instead, you may want to use a reverse proxy in front of your Go program, so that it listens on port 80 and and connects to your program on port, say, 4000. There are many reason for doing the latter: not having to run your Go program as root, serving other websites/services on the same host, SSL termination, load balancing, logging, etc.
I use HAProxy in front. Any reverse proxy could work. Nginx is also a great option (much more popular than HAProxy and capable of doing more).
global log 127.0.0.1 local0 maxconn 10000 user haproxy group haproxy daemon defaults log global mode http option httplog option dontlognull retries 3 timeout connect 5000 timeout client 50000 timeout server 50000 frontend http bind :80 acl is_stats hdr(host) -i hastats.myapp.com use_backend stats if is_stats default_backend myapp capture request header Host len 20 capture request header Referer len 50 backend myapp server main 127.0.0.1:4000 backend stats mode http stats enable stats scope http stats scope myapp stats realm Haproxy\ Statistics stats uri / stats auth username:password
Nginx is even easier.
Regarding service control, I run my Go program as a system service. I think everybody does that. My server runs Ubuntu, so it uses Upstart. I have put this at /etc/init/myapp.conf for Upstart to control my program:
start on runlevel  stop on runlevel [!2345] chdir /home/myapp/myapp setgid myapp setuid myapp exec ./myapp start 1>>_logs/stdout.log 2>>_logs/stderr.log
Another aspect is deployment. One option is to deploy by just sending binary file of the program and necessary assets. This is a pretty great solution IMO. I use the other option: compiling on server. (I’ll switch to deploying with binary files when I set up a so-called “Continuous Integration/Deployment” system.)
I have a small shell script on the server that pulls code for my project from a remote Git repository, builds it with Go, copies the binaries and other assets to ~/myapp/, and restarts the service.
Overall, the whole thing is not very different from any other server setup: you have to have a way to run your code and have it serve HTTP requests. In practice, Go has proved to be very stable for this stuff.
- Reverse HTTP proxy to my Go application
- Static file handling
- SSL termination
- HTTP headers (Cache-Control, et. al)
- Access logs (and therefore leveraging system log rotation)
- Rewrites (naked to www, http:// to https://, etc.)
nginx makes this very easy, and although you can serve directly from Go thanks to net/http, there's a lot of "re-inventing the wheel" and stuff like global HTTP headers involves some boilerplate you can probably avoid.
supervisord for managing my Go binary. Ubuntu's Upstart (as mentioned by Mostafa) is also good, but I like supervisord as it's relatively distro-agnostic and is well documented.
Supervisord, for me:
- Runs my Go binary as needed
- Brings it up after a crash
- Holds my environmental variables (session auth keys, etc.) as part of a single config.
- Runs my DB (to make sure my Go binary isn't running without it)
For those who want simple go app running as a daemon, use systemd (Supported by many linux distros) instead of Upstart.
Create a service file at
[Unit] Description=My Go App [Service] Type=simple WorkingDirectory=/my/go/app/directory ExecStart=/usr/lib/go run main.go [Install] WantedBy=multi-user.target
Then enable and start the service
systemctl enable my-go-daemon systemctl start my-go-daemon systemctl status my-go-daemon
systemd has a separate journaling system that will let you tail logs for easy trouble-shooting.
You can bind your binary to a socket to Internet domain privileged ports (port numbers less than 1024) using setcap
setcap 'cap_net_bind_service=+ep' /path/to/binary
- This command needs to be escalated. sudo as necessary
- Every new version of your program will result in a new binary that will need to be reauthorized by setcap