The dust is settling around my development sandbox for 2016. I've got what feels like a good mix of monitoring, scalability, and cost (about $10 / month) - lots of juice for the squeeze.
Here's a guide through most of what I have set up and how you can do it yourself. My primary goal was to have an online presence that is fairly scalable with low costs. I'll talk about how to set up SSH and users, Git-based version control, deploy automation, and load balancing. I'll briefly touch on service management, deploy scripts, and reverse proxies, then we'll call it a day.
I've settled on Digital Ocean, a VPS provider with pretty good pricing and great community articles. Digital Ocean charges per hour, so it won't cost you much to play around.
This guide will use the smallest instance (droplets), weighing in at 512MB of memory and 20GB disk space, running the default
Ubuntu 15.04 x64 images. I'll refer to the IP of this instance as
PuTTYgen is a good tool to make SSH keys in Windows (it's bundled with PuTTY). We'll make two keys: one for user
root, and one for user
deploy, who will own everything running on the commit and deploy chain.
Generateand move the mouse for a while
Key commentif you like
Public key for pasting into OpenSSH authorized_keys filearea
key-root.txtfile in the Documents folder for your Windows user
Save private keyand save
key-root.ppkto the Documents folder (no passphrase!)
On Windows, various programs (like Git) will look in your Documents folder for SSH keys, so it's good to save them there.
key-root.txtto your Digital Ocean profile's security area.
Ubuntu 15.04 x64image and the
Use PuTTY to SSH into the new droplet:
Connection -> SSH -> Authto add
Private key for authenticationsection.
...and create the
adduser --disabled-password deploy
sudo su deploy
mkdir ~/.ssh && cd ~/.ssh
authorized_keysand open for editing
authorized_keys, save and close.
The new user should have
visudoand look for the
User privilege specificationsection
deploy ALL=(ALL:ALL) NOPASSWD:ALL, save and exit.
Now, you should be able to make a new profile in PuTTY to SSH in as
deploy and run
sudo commands without requiring a password.
There's various programs to wrap the PuTTY profile tool into tabbed interfaces with various extras. My current favorite is SuperPuTTY.
Since you're running a tiny environment with limited memory available, you'll need to set up some swap space to give a little extra capacity to the memory. A good rule of thumb:
Swap should equal 2x physical RAM for up to 2 GB of physical RAM, and then an additional 1x physical RAM for any amount above 2 GB, but never less than 32 MB.
Digital Ocean has a good guide on adding swap. It's worth reading. Here's the distilled content:
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
sudo swapon -s
/swapfile none swap sw 0 0
vm.swappiness = 20 vm.vfs_cache_pressure = 50
There's a few dependencies required for the various software installs below, so knock them out in one fell swoop:
sudo apt-get update
sudo apt-get install -y docker.io libsqlite3-dev git mysql-server vim
Note that MySQL requires a password to be set for its root user, which will be used later.
Go is required to run GOGS ("Go Git Service").
sudo tar -C /usr/local -xzf go1.5.1.linux-amd64.tar.gz
I tried Gitlab earlier this year, and it was too demanding for tiny instances with limited resources. That said, it was a good experience - they have a CI runner built in and a straightforward installation process. It's a great resource for small or medium-size organizations, but not for my one man show.
GOGS is the lightweight go-to solution at the moment. It's an adolescent project, written in Go, that provides a great way to host Git version control. I cobbled together this process from a few install guides.
rootMySQL user to create the
mysql -u root -p -e 'DROP DATABASE IF EXISTS gogs; CREATE DATABASE IF NOT EXISTS gogs CHARACTER SET utf8 COLLATE utf8_general_ci;'
go get github.com/gogits/gogs. It may take a while, with no notifications.
go buildAgain, wait a bit, and no notifications.
If everything went well, it's now time to configure GOGS. These settings and others are stored in the custom configuration file (at
/home/deploy/go/src/github.com/gogits/gogs/custom/conf/app.ini) and can be manually edited later.
Database Settings, update the username to
Database Settings, update the password for the root user.
Application: General Settings, update the Run User to
Application: General Settings, change the
Application URLto the IP of the instance (192.0.2.1)
At this point, you'll have to kill the GOGS process, and it becomes apparent we need a service. There are several ways to get GOGS up and running as a daemon when the system starts.
Upstart manages scripts in the
/etc/init folder, but is being "transitioned" out of Ubuntu. Upstart starts processes greedily, triggered by events, whether they are needed or not. Its replacement, Systemd, starts processes lazily, triggered by dependencies. A third option is Supervisor, which prefers the role of "service manager" rather than "init manager".
Our continuous deployer, Drone, uses either Upstart or Systemd, so we'll use them too. I'll show both here in case you prefer Ubuntu 14. (If you're interested in Supervisor, here's how to do that.)
sudo vim /lib/systemd/system/gogs.serviceand add the following:
[Unit] Description=Gogs (Go Git Service) After=syslog.target After=network.target [Service] Type=simple User=deploy Group=deploy WorkingDirectory=/home/deploy/go/src/github.com/gogits/gogs ExecStart=/home/deploy/go/src/github.com/gogits/gogs/gogs web Restart=always Environment=USER=deploy HOME=/home/deploy [Install] WantedBy=multi-user.target
sudo systemctl enable gogs.service
sudo service gogs start
sudo service gogs status
sudo vim /etc/init/gogs.confand add the following:
start on started mysql console log env USER=deploy script export HOME=/home/$USER export GOPATH=/home/deploy/go export PATH=/sbin:/usr/sbin:/bin:/usr/bin:$GOPATH/bin chdir $GOPATH/src/github.com/gogits/gogs exec ./gogs web end script
sudo service gogs start
sudo service gogs status
sudo tail -f /var/log/upstart/gogs.log
Boom - version control. Now to add deployment automation.
Drone is a lightweight continuous deployment tool that adds Git hooks to watch your commits. It then triggers builds from a build file that is in your project. They recently released
Drone 0.4, with new docs and easier installation. It's a great project that's on the up-and-up.
Create the config file at
/etc/drone/dronerc, and add the following configuration:
REMOTE_DRIVER=gogs REMOTE_CONFIG=http://192.0.2.1:3000 DATABASE_DRIVER=sqlite3 DATABASE_CONFIG=/var/lib/drone/drone.sqlite
Pull the Docker image:
docker pull drone/drone:0.4, and fire up the Docker container:
sudo docker run --volume /var/lib/drone:/var/lib/drone --volume /var/run/docker.sock:/var/run/docker.sock --env-file /etc/drone/dronerc --restart=always --publish=4000:8000 --detach=true --name=drone drone/drone:0.4
Note that in the installation docs, Drone is published to
:80 by default. Our load balancer will be listening on that port for all incoming traffic, so Drone has to be served from a different port - we'll use
You should now be able navigate to
192.0.2.1:4000 and see something like this:
You can now log in with your GOGS account. Drone will automatically synchronize publically available repos, and you can activate the ones you wish to watch. They're triggered by deployment scripts.
You may be thinking, "If both Drone and GOGS have support for both MySQL and SQLite, why don't we configure them to use the same database software, and get a performance increase?" The answer, for now, is surprisingly clear.
go build -tags "sqlite". The dependencies introduced here can't run on a tiny instance.
Configuration files are popular at the moment in continuous deployment solutions for good reason:
If you've used Jenkins, you know that anything in the above list takes some doing to accomplish. Tools like Gitlab CI and Travis CI use configs written in YAML. I've found it to be a great approach.
Drone offers a variety of ways to work with its Docker container when the build task runs. A very basic configuration looks like this:
Once this is run, you'll see your commands being executed in the build output. The last line runs a shell script you can create in the root of your project. Similar to Jenkins, I've found it's easiest to have a shell script to run, mostly because they support variables to hold server IPs or repository URIs.
build: image: golang commands: - echo hello from drone.yml - whoami - ./your-deploy-script.sh
FROM ubuntu RUN apt-get -y --install-recommends --install-suggests update RUN apt-get -y --install-recommends install git
Here's a few helpful commands to help work with Docker:
# Use a Dockerfile to build an image with a (t)ag. sudo docker build -t IMAGENAME /absolute/path/to/dockerfile # Run Bash that is (i)nteractive and outputs to (t)erminal sudo docker run -it IMAGENAME /bin/bash # Output report of all images, filter for untagged, and for each one, remove. sudo docker rmi $(docker images | grep "^<none>") # Output report of (a)ll containers (IDs only using the -q flag) and for each one, remove. sudo docker rm $(docker ps -aq)
This example assumes you have a domain name ("YOURDOMAIN.COM") and it's registered to your droplet IP. You should be able to reach GOGS on
sudo apt-get install haproxy
/etc/haproxy/haproxy.cfgand enter the following:
global log /dev/log local0 log /dev/log local1 notice chroot /var/lib/haproxy stats socket /run/haproxy/admin.sock mode 660 level admin stats timeout 30s user haproxy group haproxy daemon defaults log global mode http option httplog option dontlognull timeout connect 5000 timeout client 50000 timeout server 50000 errorfile 400 /etc/haproxy/errors/400.http errorfile 403 /etc/haproxy/errors/403.http errorfile 408 /etc/haproxy/errors/408.http errorfile 500 /etc/haproxy/errors/500.http errorfile 502 /etc/haproxy/errors/502.http errorfile 503 /etc/haproxy/errors/503.http errorfile 504 /etc/haproxy/errors/504.http frontend http-in mode http bind *:80 acl gogs_request hdr(host) gogs.YOUR_DOMAIN.COM use_backend gogs if gogs_request default_backend YOUR_SERVER_POOL backend gogs http-request set-header X-Forwarded-Port %[dst_port] server gogs YOURDOMAIN.COM:3000 check backend YOUR_SERVER_POOL mode http balance roundrobin #option forwardfor http-request set-header X-Forwarded-Port %[dst_port] option httpchk HEAD / HTTP/1.1\r\nHost:localhost server YOUR_SERVER_01 192.0.2.1:80 check server YOUR_SERVER_02 192.0.2.2:80 check
Basically, HAProxy listens for HTTP requests on
:80, checks if they match an (a)ccess (c)ontrol (l)ist, and routes to a specific backend or a default.
The config is fairly readable; any unclear commands are explained pretty well in the documentation. You can see that HAProxy works as a reverse proxy server, distributing requests to backends. You can experiment and make addresses for Drone, and the stats monitoring page (search for "Monitoring HAProxy") served by HAProxy.
Most important, you can now add as many servers as you want and the traffic will be balanced across them. Go forth and scale!