Custom Domain: Gitlab Pages + Digital Ocean DNS + Let’s Encrypt.

April 14, 2019


Why am I choosing this route.

The outcome I’m expecting… An automated process for renewing my SSL certificate for my website https://thecb4.io

 HTTPS (HTTP Secure) is transitioning from optional to required across the internet for websites and APIs. Browsers are starting to warn of insecure websites. Apple has started requiring any link accessed in an app to be HTTPS unless you specifically opt out. In order for your website to use HTTPS, you have to have SSL certificates. These certificates confirm your ownership of the domain and that the domain itself can be trusted. Historically, you would obtain SSL certificates through your domain registrar and it was expensive; several hundred dollars if you want to include a wildcard (all subdomains). Recently The Linux Foundation has supported a project called Let’s Encrypt. This provides SSL certificate issuance for free in order to drive adoption. The caveat is that these certificates need to be reviewed on a more frequent basis (every 3 months) than historical certificates (annually). I will be using the certbot command line tool and hosting my website on Gitlab Pages.

Gitlab provides free hosting for static websites similar to GitHub. Gitlab has the advantage of having CI/CD as part of their tooling infrastructure. This allows me to better manage the workflow for deploying my website. Both GitHub and Gitlab require docs to be in a certain directory in order to serve up on pages. With Gitlab I don’t have to have that directory in source control. I can create that directory and copy files into it as part of the deployment process. This also requires me to understand how to deploy a website. My website is very straight forward, I'm simply copying files into a directory.

Much of the work from here on out is a derivative of the work completed by Michael Ummels. I would like to personally thank him for going through the process initially. He provided a framework for using an additional step for GitLab CI that will run the Certbot command on a recurring basis automatically and post the certs to GitLab Pages. Ummels leverages the dns challenge from his DNS service to produce the certificates. I will do the same thing using Digital Ocean.

I love Digital Ocean. They provide several great services, from generic virtual servers all the way to deploying a Kubernetes cluster. Their pricing is great and they good command line tools. One of their features is managing DNS (Domain Name Servers). I can have TLD (top level domain) from a registrar, but all of the aliases, redirects, routing can occurring with Digital Ocean. Why is that important? Digital Ocean has a dns challenge plugin for certbot that allows me to do a dns challenge based request for my SSL certificate. I [simply] have to point Gitlab and Digital Ocean at each other and then certbot can continue the conversation on an ongoing basis through a scheduled CI action. It’s quite a few steps. I’ve included some screen shots to assist. This article is being written in Spring of 2019. Some things may change by the time you read this. I will note which versions of the toolchain I am using.

Working Configuration

Alright. Let's get started.

  1. Create Gitlab Personal Access Token. Needed to access the gitlab API alt text
  2. Create Digital Ocean access token. Needed to access the DO API alt text  
  3. Create repo for project… for pages you have two choices (https://docs.gitlab.com/ee/user/project/pages/).
    1. Public folder
    2. CI to deploy to public folder. I chose this option because I don’t want to maintain a “public” folder in my repo. This is done by including a gitlab-ci.yaml file in the repository with the following content.
stages:
  - deploy

pages:
  image: alpine:latest
  stage: deploy
  script:
    - mkdir public
    - cp -a deployment/client/. public/
  artifacts:
    paths:
      - public
  only:
    - master
  1. Get a domain (Namecheap) from a registrar. Some as cheap at $10 annually.

  2. Add domain to gitlab

    1. Turn off force SSL (for now)
    2. Add domain name alt text
    3. Leave certificates blank. You will fill them in via API later
    4. Capture TXT record that needs to be added to DNS to let gitlab verify your ownership of the domain alt text
    5. Ignore CNAME instructions… you will use an IP address. Why? Digital ocean does not support CNAME for top level domains. You need to use an IP address for top level.
  3. Add Domain to Digital Ocean alt text "Add Domain to Digital Ocean")

  4. Use Digital Ocean Nameservers for Namecheap registrar. If you have mail through your registrar, it’s ok. You can point DNS records back at those servers. The Digital Ocean Name Servers are ns[1-3].digitalocean.com

  5. Add an A Record to Digital Ocean pointing to gitlab pages server IP (instead of CNAME). At the time of writing this. The IP Address is 35.185.44.232

doctl compute domain records create space-cowboy.io --record-type A --record-name @ --record-data 35.185.44.232 --record-ttl 1800
  1. Add TXT record from gitlab so that gitlab can verify ownership.
doctl compute domain records create space-cowboy.io --record-type TXT --record-name space-cowboy.io --record-data "_gitlab-pages-verification-code.space-cowboy.io TXT gitlab-pages-verification-code=aaaabbbbbcccccdddeeeee" --record-ttl 1800
  1. Add cert update CI step. This is where all the magic happens.
    1. Obtain cert. Validate ownership with Digital Ocean DNS using Digital Ocean Personal Access Token.
    2. Post SSL docs to gitlab with Gitlab Personal Access Token.

Both personal access tokens are stored as variables in Gitlab, specifically for CI.

This becomes another stage in the gitlab-ci.yaml file. We also revise the original deploy stage to ignore the schedule.

variables:
  LC_ALL: "C.UTF-8"
  DOMAIN: "space-cowboy.io"
  EMAIL: "cavelle@thecb4.io"

stages:
  - deploy
  - update

pages:
  image: alpine:latest
  stage: deploy
  script:
    - mkdir public
    - cp -a deployment/client/. public/
  artifacts:
    paths:
      - public
  only:
    - master
  except:
    - schedules

update_cert:
  image:
    name: thecb4/gitlabcertbotdigitalocean
    entrypoint: [""]
  variables:
    CERT_FILE: "/etc/letsencrypt/live/$DOMAIN/fullchain.pem"
    KEY_FILE: "/etc/letsencrypt/live/$DOMAIN/privkey.pem"
  stage: update
  before_script:
    - echo "Adding digital ocean token"
    - echo "dns_digitalocean_token = $DIGITAL_OCEAN_TOKEN" > .secret
    - chmod 600 .secret
  script:
    - bin/certbot.sh "$DOMAIN" "$EMAIL"
    - "curl --silent --fail --show-error --request PUT --header \"Private-Token: $GITLAB_CI_TOKEN\" --form \"certificate=@$CERT_FILE\" --form \"key=@$KEY_FILE\" https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/pages/domains/$DOMAIN"
  only:
    - schedules
#!/usr/bin/env sh

certbot certonly \
--non-interactive \
--agree-tos \
--dns-digitalocean --dns-digitalocean-credentials .secret \
--dns-digitalocean-propagation-seconds 60 \
--email "$2" \
-d "$1" \
-d "*.$1"
  1. Create schedule in gitlab (1xmonth) to run CI step.
  2. alt text
  3. Trigger schedule… you should see the certificate info in gitlab
  4. Check “force https” now that you can see certificate info
  5. Go get a drink.

Did I achieve my outcome? I have a custom TLD with HTTPS that is hosted in Gitlab using Let’s Encrypt for SSL that auto-renews every month with a DNS challenge against Digital Ocean’s name servers. I never have to touch the renew step again. I can focus on publishing content.

What did I learn:

This is very specific solution to the problem. However you can use these three services in multiple ways that I will get into in later posts.

Questions, comments, concerns? Find me on Twitter. @thecb4