Deploy a server with docker and four lines of code.
Through trial and effort, deploying the stack hosting Bonner.is
involves four lines of bash code.
git clone https://github.com/moritonal/bonner-deploy.git
cd bonner-deploy
nano .env maybe-chat-server.env
docker-compose up
This is because of the fairly amazing power of the docker-compose.yml
file.
Bonner.is
serves two kinds of services (HTTPS and HTTP) over ports 80 and 445. To allow many services to share these two ports we have a load-balancer that listens for connections on these ports, and then passes the connection into the relevant service for a particular hostname. To make things interesting, the load-balance is the first Docker container we define.
version: "2"
services:
nginx-proxy:
image: jwilder/nginx-proxy
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro
- certs:/etc/nginx/certs:ro
- vhost:/etc/nginx/vhost.d
- html:/usr/share/nginx/html
- ./vhost.d:/etc/nginx/vhost.d:ro
labels:
com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy: ""
nginx-proxy-companion:
image: jrcs/letsencrypt-nginx-proxy-companion
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- certs:/etc/nginx/certs:rw
- vhost:/etc/nginx/vhost.d
- html:/usr/share/nginx/html
depends_on:
- "nginx-proxy"
- "bonner"
The wall of text above is the opening of our docker-compose.yml
which defines an nginx-proxy
image which'll act as our load-balancer, and a second image called nginx-proxy-companion
which handles requesting the HTTPS certificates from Let's Encrypt.
You can see the shared volumes between the two containers which allows them to interact. Volume are how state is maintained across containers.
Notice also how both have access to the docker.sock
which is what the Docker clients use to talk to the Docker service, and therefore lets them interact with the host system and become self-aware of the other services.
Now we'll define our first service.
bonner:
build:
context: "https://${GITHUB_USERNAME}:${GITHUB_AUTH_TOKEN}@github.com/moritonal/bonner.is.git"
environment:
VIRTUAL_HOST: bonner.is,tom.bonner.is
LETSENCRYPT_HOST: bonner.is,tom.bonner.is
LETSENCRYPT_EMAIL: [email protected]
The service above is named bonner
, and the build
line means that the docker-compose engine will understand it needs to build the service before deploying it. When docker-compose is told to deploy the server, it'll perform the following steps:
- Download the code from the GitHub repo using a Username and Password stored in the
.env
file alongside this. - Build the Dockerfile (find the blog for it here) stored within that repo, resulting in an image. This would be considered the CI step from conventional systems.
- Host the image into a container, setting three environment variables within it.
- The
nginx-proxy
will detect a new container has started, and if it has a valid host, direct traffic towards it's sub-domain. - The
nginx-proxy-companion
will be notified of a new domain, and attempt will attempt to request a certificate from Let's Encrypt.
This means that the process of adding/linking a sub-domained service is wholly contained on the 7 lines above and an update to the depends_on
from nginx-proxy-companion
. Any changes to how the service host's itself is contained within it's own Dockerfile. Each service can choose how to host itself and what to use, such as the following:
maybe-chat-socket:
build:
context: "https://${GITHUB_USERNAME}:${GITHUB_AUTH_TOKEN}@github.com/moritonal/maybe-chat-server.git"
env_file:
- maybe-chat-server.env
environment:
VIRTUAL_HOST: maybe-chat-socket.bonner.is
LETSENCRYPT_HOST: maybe-chat-socket.bonner.is
LETSENCRYPT_EMAIL: [email protected]
This service is completely different to bonner
, in that it's a node express server that sits at maybe-chat-socket.bonner.is
. It's also a private repo, therefore requiring the GITHUB_AUTH_TOKEN
to access, and it has an environment file which is passed in for it to use internally for settings.
If you view the full docker-compose.yml
file you'll find 10 services hosted at bonner.is
, all built and deployed using a single file. This get's even more fun when we take a look at how the github-integration
services allows us to redepoy a service whenever it changes.
You can read that blog here.