Expose Synpse application with DDNS, CloudFlare and Let's Encrypt

Published on September 18, 21

In the previous blog post we showed how to expose an application with the free DuckDNS domain. This is a second article showing how to use a custom domain with a very similar application stack.

Important: This will not work if your devices are not able to be accessed via an external IP address. For this to work you might need to configure your router with a port forwarding. Which is out of scope for this blog post.

###Technologies used

  1. Synpse for hosting and running applications anywhere
  2. DDNS for managing DNS records for multiple DNS providers
  3. CloudFlare for hosting our DNS records
  4. Let’s Encrypt for TLS certificates

###Domain

In our case, we own a domain already. Section bellow shows how to integrate it with CloudFlare.

Firstly, you will need a custom domain. You can buy one from any domain provider of your choice. We used GoDaddy to acquire a domain faros.sh, but because DDNS do not support “GoDaddy” (shame on you!), we will manage all our DNS via “CloudFlare” (this is what everybody should be doing either way…). This is how it looks like on “GoDaddy” site:

GoDaddy configuration
GoDaddy configuration

We configure “GoDaddy” to point to the “CloudFlare” nameservers, provided to us when we onboarded the domain to the “CloudFlare”. Once you follow “CloudFlare” onboarding of the domain, you should be able to see it in the “CloudFlare” menu:

CloudFlare domain
CloudFlare domain

###Deploy basic application

As an application, we will use quay.io/synpse/hello-synpse-go “Hello World” application. We are going to deploy the “Hello World” application alone for now:

Hello world
Hello world

As a yaml:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
name: hello
scheduling:
  type: Conditional
  selectors:
    app: ddns
spec:
  containers:
    - name: hello
      image: quay.io/synpse/hello-synpse-go
      ports:
        - 8080:8080

We can access the application on our local network, but this is not our final goal:

1
2
3
4
5
6
7
8
9
$ curl 192.168.178.103:8080
<!DOCTYPE html>
<html lang="en">
  <head> </head>
  <body>
    <h1>Hello from Synpse</h1>
    
  </body>
</html>

###Extend an application with DDNS

We are going to use linuxserver docker image for DDNS client.

Create DDNS config for CloudFlare

If you are not using a custom domain - skip to next the section

We got the example from the command below. If you use a different provider, this is how you can get it out. We have prepared an example for you here below.

1
docker run linuxserver/ddclient ddclient --help | grep cloudflare -A 10

Our example config looks like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# /etc/ddclient/ddclient.conf
#
protocol=cloudflare
daemon=60
syslog=yes
ssl=yes
ttl=2
use=web
web=checkip.dyndns.org
server=api.cloudflare.com/client/v4
zone=faros.sh
login=[email protected]
password=9e64d21647xxxxxxxxxxxxxxxxxxxxxxx
faros.sh 

Where the API key is API key from CloudFlare:

CloudFlare API key
CloudFlare API key

Important: Make sure you add an API key NOT API TOKEN! DDNS not yet supports API Tokens!!!

Precreate DNS A name with the root (@) record in cloudflare

Once you have the config file created, create a secret for the config file using Synpse CLI:

1
synpse secret create ddns-config -f ddclient.conf

Extend “Hello world” application with DDNS image:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
name: hello
scheduling:
  type: Conditional
  selectors:
    app: ddns
spec:
  containers:
    - name: hello
      image: quay.io/synpse/hello-synpse-go:amd64
      forcePull: true
      networkMode: default
      ports:
        - 8080:8080
    - name: ddns
      image: ghcr.io/linuxserver/ddclient
      env:
        - name: TZ
          value: Europe/London
        - name: PUID
          value: "0"
        - name: PGID
          value: "0"
      secrets:
        - name: ddns-config
          filepath: /config/ddclient.conf

Once these steps are done, we should have:

  1. Application deployed and exposed on port 8080;
  2. DDNS running and managing CloudFlare configuration.

In addition for this to look like an finished application, we want TLS certificates!
For this, we will add “Let’s Encrypt” container to the mix and we will use CertBot

###Let’s Encrypt with CloudFlare

Let’s create a configuration for “CertBot” (same credentials as in DDNS)

1
2
3
# Cloudflare API credentials used by Certbot
dns_cloudflare_email = [email protected]
dns_cloudflare_api_key = 9e64d21647xxxxxxxxxxxxxxxxxxxxxxx

Create a secret for CertBot using Synpse CLI:

1
synpse secret create cert-bot -f certbot.ini

We are going to create a small script to make sure renew the certificate on a periodic basis as certbot is not able to run as a daemon, which is required for containers:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/bin/sh

# Small hack script to renew "Let's Encrypt" certs each day
while [ : ]
do
    echo "Renewing Let's Encrypt certs"
    certbot certonly \
    --dns-cloudflare \
    --dns-cloudflare-credentials /run/secrets/certbot.ini \
    --email [email protected] \
    --dns-cloudflare-propagation-seconds=20 \
    --renew-by-default  \
    --agree-tos \
    # remove this if you are running tests. otherwise "Let's Encrypt" rate limit will ban you very fast!
    #--test-cert \
    -d faros.sh \
    -d www.faros.sh \
    -n 

    echo "Sleeping for 7d"
    sleep 7d  
done
1
synpse secret create script-cert-bot -f ddns/renew.sh

and extend our existing application. Note variables we added to the original application and change of port.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
name: hello
scheduling:
  type: Conditional
  selectors:
    app: ddns
spec:
spec:
  containers:
    - name: hello
      image: quay.io/synpse/hello-synpse-go:amd64
      forcePull: true
      networkMode: default
      ports:
        - 443:443
      volumes:
        - /data/demo/letsencrypt:/etc/letsencrypt
      env:
        - name: TLS_CRT
          value: /etc/letsencrypt/live/faros.sh/fullchain.pem
        - name: TLS_KEY
          value: /etc/letsencrypt/live/faros.sh/privkey.pem
        - name: PORT
          value: :443
      restartPolicy: {}
    - name: ddns
      image: ghcr.io/linuxserver/ddclient
      env:
        - name: TZ
          value: Europe/London
        - name: PUID
          value: "0"
        - name: PGID
          value: "0"
      secrets:
        - name: ddns-config
          filepath: /config/ddclient.conf
      restartPolicy: {}
    - name: certbot
      image: certbot/dns-cloudflare
      command: /run/secrets/renew.sh
      entrypoint:
        - sh
      volumes:
        - /data/demo/letsencrypt:/etc/letsencrypt
        - /data/demo/var-lib-letsencrypt:/var/lib/letsencrypt
      secrets:
        - name: cert-bot
          filepath: /run/secrets/certbot.ini
        - name: script-cert-bot
          filepath: /run/secrets/renew.sh
      restartPolicy: {}

Once the application is updated, rolled out and all components are running you should be able to see this:

Application deployed
Application deployed

We used development “Let’s Encrypt” mode so the certificate is showing as not valid.

./wrap_up.sh

This shows one more way to expose your application to the outside world. This can be used for any other applications like “Drone”, “Prometheus”, “Grafana”. We will use this pattern in the future to show how you can deploy other applications!

If you have any questions or suggestions, feel free to start a new discussion in our forum or drop us a line on Discord