Repository: https://github.com/timk153/docker-external-dns
This project was inspired by:
https://github.com/kubernetes-sigs/external-dns
https://github.com/dntsk/extdns
It is broadly similar to extdns in functionality, but expands upon it.
This project was built using:
Nest framework TypeScript starter repository.
This project does the following:
IMPORTANT Only CloudFlare is supported.
The project provides a Docker Compose external DNS container specifically for CloudFlare. It supports DNS entries of types A, CNAME, NS, and MX. Configuration is managed through environment variables and labels applied to Docker containers. Detailed examples for various configurations and DNS record types are provided in the Examples section. For more details on DNS record types, refer to the DNS Entry Types section.
Check your supplied LOG_LEVEL.
Ensure it is one of: 'log', 'error', 'warn', 'debug', 'verbose', 'fatal'
If set to an invalid value the project will hand at start-up. I've tried to address this behavior unsuccessfully. For now awareness is the simplest solution.
Using the Docker Compose External DNS container is very straightforward. You need to declare an instance of it within your Docker Compose definition (docker-compose.y(a)ml) with the appropriate volume mount and environment variables set. Then, add some labels to your containers.
This file comprises the following sections:
The container is configured via environment variables. The following table describes the variable name, its default value (if any), and what it does. Detailed examples are available in the Examples section.
Variable Name | Default Value | Description |
---|---|---|
PROJECT_LABEL | docker-compose-external-dns | Detailed example available in the project Label and Instance ID section section. Forms part of the label the project looks for on Docker containers to interpret as DNS entries. Also written as a comment to Cloudflare DNS entries managed by this instance of the project. |
INSTANCE_ID | 1 | Detailed example available in the project Label and Instance ID section section. Forms part of the label the project looks for on Docker containers to interpret as DNS entries. Also written as a comment to Cloudflare DNS entries managed by this instance of the project. |
EXECUTION_FREQUENCY_SECONDS | 60 | How frequently the CRON job should execute to detect changes. Default is every 60 seconds. Undefined or empty uses the default. Minimum is every 1 second. There is no maximum. This must be an integer. |
DDNS_EXECUTION_FREQUENCY_MINUTES | 60 | Determines how frequently the DDNS Service checks for a new public IP address. This setting only applies if you're using DDNS otherwise the service will not be started. |
API_TOKEN | You must supply either API_TOKEN or API_TOKEN_FILE but not both. Your API token from Cloudflare. Must be granted Zone.Zone read and Zone.DNS edit. IMPORTANT Use of this property is insecure as your API_TOKEN will be in plain text. It is recommended you use API_TOKEN_FILE. Use at your own risk. |
|
API_TOKEN_FILE | You must supply either API_TOKEN or API_TOKEN_FILE but not both. Secure way to share your Cloudflare API Token with the project. Recommended approach for Docker Swarm. Compatible with Docker Compose (but less secure). Read Docker Compose docs for more information: Docker Compose Secrets |
|
LOG_LEVEL | error | The current logging level. The default is error, meaning only errors and fatal get logged. Each level includes the levels above it from most specific to least specific. By way of example, verbose will output everything. debug will ignore verbose. log will ignore debug and verbose. From most specific to least:fatal error warn log debug verbose These log levels come from the NestJS project. |
These two items combine to form the label which the project will look for on Docker Containers and write as the comment for that DNS entry into Cloudflare.
It is interpolated as follows: ${PROJECT_ID}:${INSTANCE_ID}
For example:
PROJECT_ID | INSTANCE_ID | EXAMPLE | Use Case |
---|---|---|---|
docker-compose-external-dns | 1 | docker-compose-external-dns:1 | The default |
docker-compose-external-dns | production | docker-compose-external-dns:production | Could be used to target a production Cloudflare subscription |
docker-compose-external-dns | production | docker-compose-external-dns:non-production | Targets the non-production Cloudflare subscription |
dns.com.mydomain | project | dns.com.mydomain:project | DNS entries for a specific subdomain of yours. |
Please note, in a large deployment the project label and instance id will become cruitial to management.
For best practice, establish a naming convension and apply it consistently.
The values of the Docker Compose labels correspond to DNS entries. You can find descriptions of the various DNS records here: List of DNS Record Types.
Examples for all of these can be found in the Examples section below. For more details on the DNS record types, refer to the DNS Entry Types section.
The A record points a domain name to an IP address.
name = example.com
server = 8.8.8.8
Lookup of example.com returns 8.8.8.8
The properties required for this entry are as follows:
property | value | description |
---|---|---|
type | A | The type of the record. In this case it should be A |
name | <your domain name> | This is the domain you want this A record to resolve for. For example: example-domain.com |
address | <your server's address (v4 or 6)> OR "DDNS" | The address you want your domain name to resolve to. Or the string literal "DDNS" which instructs the project to compute your current public ipv4 address and use it for this record. |
proxy | true or false | True uses Cloudflare's proxy to hide your address. False causes Cloudflare to act as a normal DNS server. Documentation: Proxied DNS Records |
The CNAME record aliases the name to another A or CNAME record. Causing the name when queried to resolve to the A record it (eventually) resolves to.
type = A
name = example.com
server = 8.8.8.8
type = CNAME
name = subdomain.example.com
target = example.com
type = CNAME
name = lower.subdomain.example.com
target = subdomain.example.com
Lookup of example.com returns 8.8.8.8
Lookup of subdomain.example.com returns 8.8.8.8
Lookup of lower.subdomain.example.com returns 8.8.8.8
The properties required for this entry are as follows:
property | value | description |
---|---|---|
type | CNAME | The type of the record. In this case it should be CNAME |
name | <your alias> | This is the alias you want this CNAME record to resolve for. For example: subdomain.example-domain.com |
target | <your full A record or CNAME record> | The full name of the relevant A or CNAME record this should resolve to. For example, use 'example-domain.com' to point at the A record above. |
proxy | true or false | True uses Cloudflare's proxy to hide your address. False causes Cloudflare to act as a normal DNS server. Documentation: Proxied DNS Records Please note, only one level of subdomain can be proxied. If it's two subdomains deep (e.g. test.subdomain.example.com) it cannot be proxied unless you have a premium subscription. |
The MX record declares that a mail server handles mail for your domain or subdomain. The name is the domain or subdomain it handles mail for. The server points to an A or CNAME which resolves to your mail server. It's common to make a CNAME record for the mail server for this entry to point to.
type = A
name = example.com
server = 8.8.8.8
type = CNAME
name = mx1.example.com
target = example.com
type = MX
name = example.com
target = mx1.example.com
Lookup of example.com returns 8.8.8.8
Lookup of mx1.example.com returns 8.8.8.8
Lookup of mail server for example.com returns 8.8.8.8
The properties required for this entry are as follows:
property | value | description |
---|---|---|
type | MX | The type of the record. In this case it should be MX |
name | <your domain> | This is the domain you want this mail server to handle mail for. For example: example-domain.com |
server | <full name of the mail server> | The full name of the relevant A or CNAME entry this should resolve to. For example 'mx1.example-domain.com' (assuming you've made a CNAME or A record resolving mx1). |
priority | 0 to 65535 | The priority of this mail server, allowing you to have more than one mail server for a domain. Must be an integer between the stated values. |
The NS record points a domain or subdomain name to an A or CNAME record. DNS queries that match that domain or subdomain are forwarded to the server. This is typically used to point traffic on a subdomain to another name server. Such as one only accessible within a private network.
type = A
name = lan.example.com
server = 192.168.0.1
type = NS
name = example.com
server = lan.example.com
Lookup of example.com returns lan.example.com
Lookup of lan.example.com returns 192.168.0.1
The properties required for this entry are as follows:
property | value | description |
---|---|---|
type | NS | The type of the record. In this case it should be NS |
name | <your domain name> | This is the domain you want to forward queries for. For example: example-domain.com |
server | <your server name> | The full name of the relevant A or CNAME entry this should resolve to. For example 'lan.example-domain.com' |
There are four types of image tag associated with this project:
tag | example | description |
---|---|---|
latest | timk153/docker-external-dns:latest | the latest release of the most recent major version |
<major version number>-latest | timk153/docker-external-dns:1-latest | the latest release of that major version. In the example it's the latest release of version 1. |
semantic version number | timk153/docker-external-dns:1.4.2 | a specific release. In the example it's release 1.4.2 |
semantic version with additional identifier | timk153/docker-external-dns:1.4.2-alpha | a alpha, beta or development build. In the example it's an alpha release of version 1.4.2. |
All available tags can be found in the docker hub public registry.
Below are a series of example configurations for the following usecases. Please note, all examples use the image tagged with latest.
This example demonstrates the most basic setup of the Docker Compose External DNS container with default values. It shows how to use the API_TOKEN environment variable and configure a single DNS entry.
IMPORTANT API_TOKEN is insecure, API_TOKEN_FILE is recommended
services:
docker-compose-external-dns:
image: 'timk153/docker-external-dns:latest'
environment:
- API_TOKEN=<your api token here>
volumes:
# Used to read labels from containers - readonly
- '/var/run/docker.sock:/var/run/docker.sock:ro'
other-service:
image: 'busybox:latest'
command: 'sleep 3600'
labels:
- 'docker-compose-external-dns:1=[{ "type": "A", "name": "my-domain.com", "address": "8.8.8.8", "proxy": false }]'
Explanation: This setup includes the docker-compose-external-dns service configured with the API_TOKEN environment variable. It will use the token to authenticate with Cloudflare. The other-service has a label that specifies a DNS A record for my-domain.com pointing to 8.8.8.8 with no proxy.
This configuration demonstrates the preferred method of passing the API token securely using Docker secrets. This is more secure than using API_TOKEN.
services:
docker-compose-external-dns:
image: 'timk153/docker-external-dns:latest'
environment:
- API_TOKEN_FILE=/run/secrets/CLOUDFLARE_API_TOKEN
secrets:
- CLOUDFLARE_API_TOKEN
volumes:
# Used to read labels from containers - readonly
- '/var/run/docker.sock:/var/run/docker.sock:ro'
other-service:
image: 'busybox:latest'
command: 'sleep 3600'
labels:
- 'docker-compose-external-dns:1=[{ "type": "A", "name": "my-domain.com", "address": "8.8.8.8", "proxy": false }]'
secrets:
CLOUDFLARE_API_TOKEN:
environment: 'CLOUDFLARE_API_TOKEN'
Explanation: This setup uses Docker secrets to securely manage the Cloudflare API token. The API_TOKEN_FILE environment variable points to the secret file, which contains the API token. This approach is recommended for better security, especially in production environments.
This example shows how to customize the label, instance ID, execution frequency, and log level settings.
IMPORTANT example uses insecure option "API_TOKEN" for simplicity.
services:
docker-compose-external-dns:
image: 'timk153/docker-external-dns:latest'
environment:
- PROJECT_LABEL=dns.com.example
- INSTANCE_ID=project-subdomain
- EXECUTION_FREQUENCY_SECONDS=120
- LOG_LEVEL=info
- API_TOKEN=<your api token here>
volumes:
# Used to read labels from containers - readonly
- '/var/run/docker.sock:/var/run/docker.sock:ro'
other-service:
image: 'busybox:latest'
command: 'sleep 3600'
labels:
- 'dns.com.example:project-subdomain=[{ "type": "CNAME", "name": "project.example.com", "target": "example.com", "proxy": true }]'
Explanation: In this configuration:
PROJECT_LABEL is set to dns.com.example, which is used as part of the label on Docker containers.
INSTANCE_ID is set to project-subdomain to differentiate this instance.
EXECUTION_FREQUENCY_SECONDS is set to 120, meaning the DNS updates will occur every 2 minutes.
LOG_LEVEL is set to info to log informational messages as well as warnings and errors.
The other-service has a CNAME record pointing project.example.com to example.com with proxy enabled.
This example illustrates managing DNS entries for multiple domains using a single docker-compose-external-dns service. Please note, your API_TOKEN(_FILE) will require permissions for both domains.
IMPORTANT example uses insecure option "API_TOKEN" for simplicity.
services:
docker-compose-external-dns:
image: 'timk153/docker-external-dns:latest'
environment:
- API_TOKEN=<your api token here>
volumes:
# Used to read labels from containers - readonly
- '/var/run/docker.sock:/var/run/docker.sock:ro'
other-service:
image: 'busybox:latest'
command: 'sleep 3600'
labels:
- 'docker-compose-external-dns:1=[
{ "type": "A", "name": "my-domain.com", "address": "8.8.8.8", "proxy": false },
{ "type": "A", "name": "my-other-domain.org", "address": "8.8.8.8", "proxy": true }]'
Explanation: This setup shows how to handle DNS records for two different domains (my-domain.com and my-other-domain.org) with one instance of docker-compose-external-dns. Each domain has its own A record configuration. The my-other-domain.org entry uses Cloudflare's proxy.
These configurations demonstrates how to manage DNS records for different domains using separate docker-compose-external-dns services.
IMPORTANT example uses insecure option "API_TOKEN" for simplicity.
services:
docker-compose-external-dns-1:
image: 'timk153/docker-external-dns:latest'
environment:
- API_TOKEN=<api token for my-domain.com here>
volumes:
# Used to read labels from containers - readonly
- '/var/run/docker.sock:/var/run/docker.sock:ro'
docker-compose-external-dns-2:
image: 'timk153/docker-external-dns:latest'
environment:
- INSTANCE_ID=2
- API_TOKEN=<api token for my-other-domain.org here>
volumes:
# Used to read labels from containers - readonly
- '/var/run/docker.sock:/var/run/docker.sock:ro'
other-service:
image: 'busybox:latest'
command: 'sleep 3600'
labels:
- 'docker-compose-external-dns:1=[{ "type": "A", "name": "my-domain.com", "address": "8.8.8.8", "proxy": false }]'
- 'docker-compose-external-dns:2=[{ "type": "A", "name": "my-other-domain.org", "address": "8.8.8.8", "proxy": true }]'
Explanation: This setup uses two separate docker-compose-external-dns services to manage DNS entries for my-domain.com and my-other-domain.org. Each service is configured with its own API token and instance ID. This allows for independent management of DNS entries for each domain.
IMPORTANT example uses insecure option "API_TOKEN" for simplicity.
services:
docker-compose-external-dns-1:
image: 'timk153/docker-external-dns:latest'
environment:
- PROJECT_LABEL=dns.com.my-domain
- API_TOKEN=<api token for my-domain.com here>
volumes:
# Used to read labels from containers - readonly
- '/var/run/docker.sock:/var/run/docker.sock:ro'
docker-compose-external-dns-2:
image: 'timk153/docker-external-dns:latest'
environment:
- PROJECT_LABEL=dns.org.my-other-domain
- API_TOKEN=<api token for my-other-domain.org here>
volumes:
# Used to read labels from containers - readonly
- '/var/run/docker.sock:/var/run/docker.sock:ro'
other-service:
image: 'busybox:latest'
command: 'sleep 3600'
labels:
- 'dns.com.my-domain:1=[{ "type": "A", "name": "my-domain.com", "address": "8.8.8.8", "proxy": false }]'
- 'dns.org.my-other-domain:1=[{ "type": "A", "name": "my-other-domain.org", "address": "8.8.8.8", "proxy": true }]'
Explanation: This setup uses two separate docker-compose-external-dns services to manage DNS entries for my-domain.com and my-other-domain.org. Each service is configured with its own API token and project label. This allows for independent management of DNS entries for each domain.
The final set of examples demonstrates different types of DNS records (A, CNAME, NS, MX) and how to configure them using Docker Compose labels. Please note, these labels may live on one or more services spead across one or more docker-compose files running on Docker. They are all on "other-service" in this instance for simplicity sake.
IMPORTANT example uses insecure option "API_TOKEN" for simplicity.
services:
docker-compose-external-dns:
image: 'timk153/docker-external-dns:latest'
environment:
- API_TOKEN=<your api token here>
volumes:
# Used to read labels from containers - readonly
- '/var/run/docker.sock:/var/run/docker.sock:ro'
other-service:
image: 'busybox:latest'
command: 'sleep 3600'
labels:
- 'docker-compose-external-dns:1=[{ "type": "A", "name": "my-domain.com", "address": "8.8.8.8", "proxy": false }]'
Explanation: This example configures an A record for my-domain.com pointing to 8.8.8.8 without using Cloudflare's proxy.
IMPORTANT example uses insecure option "API_TOKEN" for simplicity.
services:
docker-compose-external-dns:
image: 'timk153/docker-external-dns:latest'
environment:
- API_TOKEN=<your api token here>
volumes:
# Used to read labels from containers - readonly
- '/var/run/docker.sock:/var/run/docker.sock:ro'
other-service:
image: 'busybox:latest'
command: 'sleep 3600'
labels:
- 'docker-compose-external-dns:1=[{ "type": "A", "name": "my-domain.com", "address": "DDNS", "proxy": false }]'
Explanation: This example configures an A record for my-domain.com. The project will start the DDNS Service when this record is processed. The service will fetch your current public ipv4 address and use it for this record. The DDNS Service will check at regular intervals for a new ipv4 address. If one is detected then this record will be updated to the new value when the next DNS synchronisation interval is reached.
Settings to control interval are explained in the configuration section.
IMPORTANT example uses insecure option "API_TOKEN" for simplicity.
services:
docker-compose-external-dns:
image: 'timk153/docker-external-dns:latest'
environment:
- API_TOKEN=<your api token here>
volumes:
# Used to read labels from containers - readonly
- '/var/run/docker.sock:/var/run/docker.sock:ro'
other-service:
image: 'busybox:latest'
command: 'sleep 3600'
labels:
- 'docker-compose-external-dns:1=[
{ "type": "A", "name": "my-domain.com", "address": "8.8.8.8", "proxy": false },
{ "type": "CNAME", "name": "sub.my-domain.com", "target": "my-domain.com", "proxy": false }]'
Explanation: This setup includes a CNAME record that aliases sub.my-domain.com to my-domain.com, following the A record configuration for my-domain.com.
IMPORTANT example uses insecure option "API_TOKEN" for simplicity.
services:
docker-compose-external-dns:
image: 'timk153/docker-external-dns:latest'
environment:
- API_TOKEN=<your api token here>
volumes:
# Used to read labels from containers - readonly
- '/var/run/docker.sock:/var/run/docker.sock:ro'
other-service:
image: 'busybox:latest'
command: 'sleep 3600'
labels:
- 'docker-compose-external-dns:1=[
{ "type": "A", "name": "lan.my-domain.com", "address": "192.168.0.1", "proxy": false },
{ "type": "CNAME", "name": "ns1.lan.my-domain.com", "target": "lan.my-domain.com", "proxy": false },
{ "type": "NS", "name": "lan.my-domain.com", "server": "ns1.lan.my-domain.com" }]'
Explanation: This configuration includes an NS record specifying ns1.lan.my-domain.com as the nameserver for lan.my-domain.com, alongside an A record for lan.my-domain.com.
IMPORTANT example uses insecure option "API_TOKEN" for simplicity.
services:
docker-compose-external-dns:
image: 'timk153/docker-external-dns:latest'
environment:
- API_TOKEN=<your api token here>
volumes:
# Used to read labels from containers - readonly
- '/var/run/docker.sock:/var/run/docker.sock:ro'
other-service:
image: 'busybox:latest'
command: 'sleep 3600'
labels:
- 'docker-compose-external-dns:1=[
{ "type": "A", "name": "my-domain.com", "address": "8.8.8.8", "proxy": false },
{ "type": "CNAME", "name": "mx1.my-domain.com", "target": "my-domain.com", "proxy": false },
{ "type": "MX", "name": "my-domain.com", "server": "mx1.my-domain.com", "priority": 0 }]'
Explanation: This example sets up an MX record for my-domain.com that points to mx1.my-domain.com with a priority of 0. This is used to specify the mail server for the domain.
services:
docker-compose-external-dns:
image: 'timk153/docker-external-dns:latest'
environment:
- API_TOKEN=<your api token here>
volumes:
# Used to read labels from containers - readonly
- '/var/run/docker.sock:/var/run/docker.sock:ro'
other-service:
image: 'busybox:latest'
command: 'sleep 3600'
labels:
- 'docker-compose-external-dns:1=[
{ "type": "A", "name": "my-domain.com", "address": "DDNS", "proxy": false },
{ "type": "CNAME", "name": "mx1.my-domain.com", "target": "my-domain.com", "proxy": false },
{ "type": "MX", "name": "my-domain.com", "server": "mx1.my-domain.com", "priority": 0 },
{ "type": "CNAME", "name": "subdomain.my-domain.com", "target": "my-domain.com", "proxy": false },
{ "type": "A", "name": "lan.my-domain.com", "address": "192.168.0.1", "proxy": false },
{ "type": "CNAME", "name": "ns1.lan.my-domain.com", "target": "lan.my-domain.com", "proxy": false },
{ "type": "NS", "name": "lan.my-domain.com", "server": "ns1.lan.my-domain.com" }]'