dream/example/z-docker-esy
2022-02-11 15:39:39 +03:00
..
2021-05-08 09:37:05 +03:00
2022-02-11 15:39:39 +03:00
2021-05-07 09:05:31 +03:00
2021-05-07 09:05:31 +03:00
2022-02-11 15:39:39 +03:00

z-docker-esy


This example wraps a very simple Web app in a Docker container, and runs it persistently, as a daemon.

let () =
  Dream.run ~interface:"0.0.0.0"
  @@ Dream.logger
  @@ Dream.router [
    Dream.get "/" (fun _ ->
      Dream.html "Dream started by Docker Compose, built with esy!");
  ]

It uses Docker Compose, so that you can quickly expand it by adding databases and other services.

version: "3"

services:
  web:
    build: .
    ports:
    - "80:8080"
    restart: always
    logging:
      driver: ${LOGGING_DRIVER:-json-file}

The example app is running live at http://docker-esy.dream.as.

The setup can be run locally or on any server provider. We will use a Digital Ocean "droplet" (virtual machine). The server binary is built by Docker.

The Dockerfile has two stages: one for building our application, and one for the runtime that only contains the final binary and its run-time dependencies.


Droplet setup

Visit Digital Ocean and create an account, then create a droplet. Simple Dream apps can be built on the smallest and cheapest droplets as of May 2021, but you may eventually need more memory. Be sure to enable monitoring and add your public SSH key.

Once the droplet starts, Digital Ocean will display its IP. We will use my-droplet as a stand-in in the example.

SSH into your droplet:

$ ssh root@my-droplet

Then, update the droplet:

$ apt update
$ apt upgrade -y

There was likely a kernel update, so restart the droplet:

$ init 6
$ ssh root@my-droplet

Install Docker:

$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add -
$ add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu focal stable"
$ apt update
$ apt install docker-ce -y

Install Docker Compose. Check here for the latest available release.

$ curl -L https://github.com/docker/compose/releases/download/1.29.2/docker-compose-Linux-x86_64 -o /usr/local/bin/docker-compose
$ chmod +x /usr/local/bin/docker-compose

At this point, you may want to create a non-root user for running your builds, We add this user to the docker group, so that it can build and start Docker containers, and give it the same SSH public key:

$ adduser build --disabled-password
$ usermod build --append --groups docker
$ usermod build --append --groups systemd-journal
$ mkdir /home/build/.ssh -m 700
$ cp .ssh/authorized_keys /home/build/.ssh/
$ chown -R build:build /home/build/.ssh

Droplet setup is now complete:

$ exit

Deploy

To deploy to the droplet, we send the sources over, and trigger the commands in deploy.sh remotely:

$ rsync -rlv . build@my-droplet:app --exclude _esy --exclude node_modules
$ ssh build@my-droplet "cd app && bash deploy.sh"

deploy.sh looks like this:

#!/bin/bash

set -e
set -x

docker-compose build
docker-compose down
docker-compose up --detach

The app should now be publicly accessible at the droplet's IP. Logs can be viewed with

$ ssh build@my-droplet "journalctl -f"

Automation

The Dream repo has a GitHub action that deploys this example to docker-esy.dream.as on every push. It runs the two commands above.

The action needs SSH access to the droplet. See Automation in z-systemd for discussion. The only difference is that we need don't need to upload the SSH key to user root, because we don't need to log in as root to start a daemon:

$ ssh-keygen -t rsa -b 4096 -f github-actions
$ ssh build@my-droplet "cat - >> .ssh/authorized_keys" < github-actions.pub

And this example uses a known_hosts secret named DIGITALOCEAN_DOCKER_ESY_KNOWN_HOSTS rather than DIGITALOCEAN_SYSTEMD_KNOWN_HOSTS, but you can pick any name you like for your version of the deploy script.


See also:

  • z-docker-opam is a variant of this example that uses opam instead of esy.
  • z-systemd packages the app as a systemd daemon, outside of a Docker container.
  • z-heroku deploys the app to Heroku.

Up to the example index