OCaml.org Deployment
OCaml.org is a single Docker container which exposes the website on port 8080. Therefore, the simplest deployment is to just run:
docker run --rm -it -p 8080:8080 ocurrent/v3.ocaml.org-server:live
This makes the website available at http://127.0.0.1:8080.
To provide HTTPS, a reverse proxy can be used such as Nginx or Caddy. We use Caddy as it has automatic certificate provisioning and renewal.
The Caddyfile
lists the expected domain names and the internal name of the Docker container. The complete file is shown below.
v3a.ocaml.org, v3b.ocaml.org, v3.ocaml.org, ocaml.org, www.ocaml.org {
reverse_proxy www:8080
}
Both Caddy and the website itself can be deployed using Docker Compose with a docker-compose.yml
file as below.
version: "3.7"
services:
caddy:
image: caddy
ports:
- "80:80"
- "443:443"
volumes:
- /etc/caddy:/etc/caddy:ro
- caddy_data:/data
- caddy_config:/config
www:
image: ocurrent/v3.ocaml.org-server:live
sysctls:
- 'net.ipv4.tcp_keepalive_time=60'
volumes:
caddy_data:
caddy_config:
Create the service as follows:
docker compose up
We use OCurrent Deployer to build the website Docker image from GitHub and deploy the update to the machine.
The update process uses docker service update --image ocurrent/v3.ocaml.org-server
which requires Docker to be in swarm mode. If the machine has an IPv6 address published in DNS, special care is needed as docker swarm init
does not listen on IPv6 addresses and ACME providers check the published AAAA records point to the target machine. As we have Caddy operating as a reverse proxy we can define Caddy as a global service with exactly one container per swarm node, and that the ports are in host mode, which publishes a host port on the node. The default of a replicated service with distributed ingress ports does not listen on IPv6. Docker Composer listens on both IPv4 and IPv6 by default.
The initial configration is performed using an Ansible Playbook as follows:
---
- hosts: all
name: Set up SwarmKit
tasks:
- docker_swarm:
listen_addr: "127.0.0.1:2377"
advertise_addr: "127.0.0.1:2377"
- hosts: v3b.ocaml.org
name: Configure controller host
tasks:
- name: create caddy directory
file:
path: /etc/caddy
state: directory
- name: configure caddy
copy:
src: Caddyfile
dest: /etc/caddy/Caddyfile
notify:
- restart caddy
- name: set up infrastructure stack
docker_stack:
name: infra
prune: yes
compose:
- version: "3.7"
services:
caddy:
deploy:
mode: global
ports:
- target: 80
published: 80
protocol: tcp
mode: host
- target: 443
published: 443
protocol: tcp
mode: host
image: caddy
volumes:
- /etc/caddy:/etc/caddy:ro
- caddy_data:/data
- caddy_config:/config
www:
image: ocurrent/v3.ocaml.org-server:live
sysctls:
- 'net.ipv4.tcp_keepalive_time=60'
volumes:
caddy_data:
caddy_config:
handlers:
- name: restart caddy
shell:
cmd: PS=$(docker ps --filter=name=infra_caddy -q) && if [ -n "$PS" ] ; then docker exec -w /etc/caddy $PS caddy reload ; fi