https://github.com/theohbrothers/docker-certbot-dns-cron
Dockerized Certbot with DNS Plugins, with cron, deploy, email alert capabilities 🐳
https://github.com/theohbrothers/docker-certbot-dns-cron
buildx certbot cron dns docker email generate-dockerimagevariants letsencrypt letsencrypt-certificates tls-certificate
Last synced: 3 months ago
JSON representation
Dockerized Certbot with DNS Plugins, with cron, deploy, email alert capabilities 🐳
- Host: GitHub
- URL: https://github.com/theohbrothers/docker-certbot-dns-cron
- Owner: theohbrothers
- License: apache-2.0
- Created: 2018-10-04T15:27:58.000Z (over 7 years ago)
- Default Branch: master
- Last Pushed: 2024-04-27T12:08:15.000Z (about 2 years ago)
- Last Synced: 2025-08-01T17:54:25.976Z (11 months ago)
- Topics: buildx, certbot, cron, dns, docker, email, generate-dockerimagevariants, letsencrypt, letsencrypt-certificates, tls-certificate
- Language: Shell
- Homepage:
- Size: 343 KB
- Stars: 5
- Watchers: 2
- Forks: 0
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# docker-certbot-dns-cron
[](https://github.com/theohbrothers/docker-certbot-dns-cron/actions/workflows/ci-master-pr.yml)
[](https://github.com/theohbrothers/docker-certbot-dns-cron/releases/)
[](https://hub.docker.com/r/theohbrothers/docker-certbot-dns-cron)
Dockerized [certbot](https://github.com/certbot/certbot) with [DNS Plugins](https://certbot.eff.org/docs/using.html#dns-plugins), based on [official certbot docker images](https://hub.docker.com/u/certbot), with cron, deploy, email alert capabilities.
It signs wildcards certificates for domains. For instance, the DNS Names for an obtained certificate for `example.com` would be: `example.com, *.example.com`.
All Certbot plugins are supported: `cloudflare`, `cloudxns`, `digitalocean`, `dnsimple`, `dnsmadeeasy`, `google`, `linode`, `luadns`, `nsone`, `ovh`, `rfc2136`, `route53`
## Deprecation notice
The present application is a 4-step tool for automating ACME certificate renewal using `certbox` for a container orchestrator like `docker` standalone or `docker` `swarm`.
However, step `2.`, `3.`, and `4.` may be solved by using already existing tools, for instance:
- Copying certs to another service can be done by sharing a volume or by some other means
- Reloading another service by sending a signal can be done in many other ways which are more secure than doing it over `/var/run/docker.sock`
- Notification can be done in many other ways other than email
Since there only remains step `1.` to solve, there is no benefit to using this application. The `certbot` tool itself constantly evolves, and it makes no sense to maintain a wrapping entrypoint script around it.
Hence, it is simpler to just use the [official certbot docker images](https://hub.docker.com/u/certbot). If a cron is needed, create a crontab in `/etc/crontabs/` and run `crond`.
## Tags
Each variant is Certbot DNS provider plugin image.
| Tag | Plugin name | Dockerfile Build Context |
|:-------:|:---------:|:---------:
| `:v1.12.0-cloudflare`, `:latest` | [certbot-dns-cloudflare](https://certbot-dns-cloudflare.readthedocs.io) | [View](variants/v1.12.0-cloudflare) |
| `:v1.11.0-cloudflare` | [certbot-dns-cloudflare](https://certbot-dns-cloudflare.readthedocs.io) | [View](variants/v1.11.0-cloudflare) |
| `:v1.10.1-cloudflare` | [certbot-dns-cloudflare](https://certbot-dns-cloudflare.readthedocs.io) | [View](variants/v1.10.1-cloudflare) |
| `:v1.9.0-cloudflare` | [certbot-dns-cloudflare](https://certbot-dns-cloudflare.readthedocs.io) | [View](variants/v1.9.0-cloudflare) |
| `:v1.12.0-cloudxns` | [certbot-dns-cloudxns](https://certbot-dns-cloudxns.readthedocs.io) | [View](variants/v1.12.0-cloudxns) |
| `:v1.11.0-cloudxns` | [certbot-dns-cloudxns](https://certbot-dns-cloudxns.readthedocs.io) | [View](variants/v1.11.0-cloudxns) |
| `:v1.10.1-cloudxns` | [certbot-dns-cloudxns](https://certbot-dns-cloudxns.readthedocs.io) | [View](variants/v1.10.1-cloudxns) |
| `:v1.9.0-cloudxns` | [certbot-dns-cloudxns](https://certbot-dns-cloudxns.readthedocs.io) | [View](variants/v1.9.0-cloudxns) |
| `:v1.12.0-digitalocean` | [certbot-dns-digitalocean](https://certbot-dns-digitalocean.readthedocs.io) | [View](variants/v1.12.0-digitalocean) |
| `:v1.11.0-digitalocean` | [certbot-dns-digitalocean](https://certbot-dns-digitalocean.readthedocs.io) | [View](variants/v1.11.0-digitalocean) |
| `:v1.10.1-digitalocean` | [certbot-dns-digitalocean](https://certbot-dns-digitalocean.readthedocs.io) | [View](variants/v1.10.1-digitalocean) |
| `:v1.9.0-digitalocean` | [certbot-dns-digitalocean](https://certbot-dns-digitalocean.readthedocs.io) | [View](variants/v1.9.0-digitalocean) |
| `:v1.12.0-dnsimple` | [certbot-dns-dnsimple](https://certbot-dns-dnsimple.readthedocs.io) | [View](variants/v1.12.0-dnsimple) |
| `:v1.11.0-dnsimple` | [certbot-dns-dnsimple](https://certbot-dns-dnsimple.readthedocs.io) | [View](variants/v1.11.0-dnsimple) |
| `:v1.10.1-dnsimple` | [certbot-dns-dnsimple](https://certbot-dns-dnsimple.readthedocs.io) | [View](variants/v1.10.1-dnsimple) |
| `:v1.9.0-dnsimple` | [certbot-dns-dnsimple](https://certbot-dns-dnsimple.readthedocs.io) | [View](variants/v1.9.0-dnsimple) |
| `:v1.12.0-dnsmadeeasy` | [certbot-dns-dnsmadeeasy](https://certbot-dns-dnsmadeeasy.readthedocs.io) | [View](variants/v1.12.0-dnsmadeeasy) |
| `:v1.11.0-dnsmadeeasy` | [certbot-dns-dnsmadeeasy](https://certbot-dns-dnsmadeeasy.readthedocs.io) | [View](variants/v1.11.0-dnsmadeeasy) |
| `:v1.10.1-dnsmadeeasy` | [certbot-dns-dnsmadeeasy](https://certbot-dns-dnsmadeeasy.readthedocs.io) | [View](variants/v1.10.1-dnsmadeeasy) |
| `:v1.9.0-dnsmadeeasy` | [certbot-dns-dnsmadeeasy](https://certbot-dns-dnsmadeeasy.readthedocs.io) | [View](variants/v1.9.0-dnsmadeeasy) |
| `:v1.12.0-google` | [certbot-dns-google](https://certbot-dns-google.readthedocs.io) | [View](variants/v1.12.0-google) |
| `:v1.11.0-google` | [certbot-dns-google](https://certbot-dns-google.readthedocs.io) | [View](variants/v1.11.0-google) |
| `:v1.10.1-google` | [certbot-dns-google](https://certbot-dns-google.readthedocs.io) | [View](variants/v1.10.1-google) |
| `:v1.9.0-google` | [certbot-dns-google](https://certbot-dns-google.readthedocs.io) | [View](variants/v1.9.0-google) |
| `:v1.12.0-linode` | [certbot-dns-linode](https://certbot-dns-linode.readthedocs.io) | [View](variants/v1.12.0-linode) |
| `:v1.11.0-linode` | [certbot-dns-linode](https://certbot-dns-linode.readthedocs.io) | [View](variants/v1.11.0-linode) |
| `:v1.10.1-linode` | [certbot-dns-linode](https://certbot-dns-linode.readthedocs.io) | [View](variants/v1.10.1-linode) |
| `:v1.9.0-linode` | [certbot-dns-linode](https://certbot-dns-linode.readthedocs.io) | [View](variants/v1.9.0-linode) |
| `:v1.12.0-luadns` | [certbot-dns-luadns](https://certbot-dns-luadns.readthedocs.io) | [View](variants/v1.12.0-luadns) |
| `:v1.11.0-luadns` | [certbot-dns-luadns](https://certbot-dns-luadns.readthedocs.io) | [View](variants/v1.11.0-luadns) |
| `:v1.10.1-luadns` | [certbot-dns-luadns](https://certbot-dns-luadns.readthedocs.io) | [View](variants/v1.10.1-luadns) |
| `:v1.9.0-luadns` | [certbot-dns-luadns](https://certbot-dns-luadns.readthedocs.io) | [View](variants/v1.9.0-luadns) |
| `:v1.12.0-nsone` | [certbot-dns-nsone](https://certbot-dns-nsone.readthedocs.io) | [View](variants/v1.12.0-nsone) |
| `:v1.11.0-nsone` | [certbot-dns-nsone](https://certbot-dns-nsone.readthedocs.io) | [View](variants/v1.11.0-nsone) |
| `:v1.10.1-nsone` | [certbot-dns-nsone](https://certbot-dns-nsone.readthedocs.io) | [View](variants/v1.10.1-nsone) |
| `:v1.9.0-nsone` | [certbot-dns-nsone](https://certbot-dns-nsone.readthedocs.io) | [View](variants/v1.9.0-nsone) |
| `:v1.12.0-ovh` | [certbot-dns-ovh](https://certbot-dns-ovh.readthedocs.io) | [View](variants/v1.12.0-ovh) |
| `:v1.11.0-ovh` | [certbot-dns-ovh](https://certbot-dns-ovh.readthedocs.io) | [View](variants/v1.11.0-ovh) |
| `:v1.10.1-ovh` | [certbot-dns-ovh](https://certbot-dns-ovh.readthedocs.io) | [View](variants/v1.10.1-ovh) |
| `:v1.9.0-ovh` | [certbot-dns-ovh](https://certbot-dns-ovh.readthedocs.io) | [View](variants/v1.9.0-ovh) |
| `:v1.12.0-rfc2136` | [certbot-dns-rfc2136](https://certbot-dns-rfc2136.readthedocs.io) | [View](variants/v1.12.0-rfc2136) |
| `:v1.11.0-rfc2136` | [certbot-dns-rfc2136](https://certbot-dns-rfc2136.readthedocs.io) | [View](variants/v1.11.0-rfc2136) |
| `:v1.10.1-rfc2136` | [certbot-dns-rfc2136](https://certbot-dns-rfc2136.readthedocs.io) | [View](variants/v1.10.1-rfc2136) |
| `:v1.9.0-rfc2136` | [certbot-dns-rfc2136](https://certbot-dns-rfc2136.readthedocs.io) | [View](variants/v1.9.0-rfc2136) |
| `:v1.12.0-route53` | [certbot-dns-route53](https://certbot-dns-route53.readthedocs.io) | [View](variants/v1.12.0-route53) |
| `:v1.11.0-route53` | [certbot-dns-route53](https://certbot-dns-route53.readthedocs.io) | [View](variants/v1.11.0-route53) |
| `:v1.10.1-route53` | [certbot-dns-route53](https://certbot-dns-route53.readthedocs.io) | [View](variants/v1.10.1-route53) |
| `:v1.9.0-route53` | [certbot-dns-route53](https://certbot-dns-route53.readthedocs.io) | [View](variants/v1.9.0-route53) |
## Usage
### Example: Not using Swarm Secrets
This example signs 2 wildcard certificates, one certificate for `example.com`, and one for `ns.example.com` :
1. `example.com`, `*.example.com`
2. `ns.example.com`, `*.ns.example.com`
```sh
docker service create --name certbot-dns-cron \
-e STAGING=1 \
-e 'DOMAINS=example.com;ns.example.com' \
-e PLUGIN_DNS_PROVIDER=cloudflare \
-e PLUGIN_DNS_CREDENTIALS_FILE=/etc/letsencrypt/certbot_dns_cloudflare_credentials.ini \
-e PLUGIN_DNS_PROPAGATION_SECONDS=10 \
--mount type=bind,source=/var/run/certbot_dns_cloudflare_credentials.ini,target=/etc/letsencrypt/certbot_dns_cloudflare_credentials.ini,readonly \
--mount type=bind,source=/path/to/data/certs/,target=/certs \
--mount type=bind,source=/path/to/data/letsencrypt,target=/etc/letsencrypt \
--replicas=1 \
theohbrothers/docker-certbot-dns-cron:v1.12.0-cloudflare
```
Contents of secret `certbot_dns_cloudflare_credentials.ini`
```ini
# Cloudflare API credentials used by Certbot
dns_cloudflare_email = cloudflare@example.com
dns_cloudflare_api_key = 0123456789abcdef0123456789abcdef01234567
```
### Example: Using Swarm Secrets
This example signs 2 wildcard certificates, one certificate for `example.com`, and one for `ns.example.com` :
1. `example.com`, `*.example.com`
2. `ns.example.com`, `*.ns.example.com`
LetsEncrypt expiry notification emails will be sent to: `admin@example.com`
```sh
docker service create --name certbot-dns-cron \
-e STAGING=1 \
--secret certbot_domains.txt \
--secret certbot_dns_cloudflare_credentials.ini \
-e PLUGIN_DNS_PROVIDER=cloudflare \
-e PLUGIN_DNS_CREDENTIALS_FILE=/run/secrets/certbot_dns_cloudflare_credentials.ini \
-e PLUGIN_DNS_PROPAGATION_SECONDS=10 \
--mount type=bind,source=/path/to/data/certs/,target=/certs \
--mount type=bind,source=/path/to/data/letsencrypt,target=/etc/letsencrypt \
--replicas=1 \
theohbrothers/docker-certbot-dns-cron:v1.12.0-cloudflare
```
Contents of secret `certbot_dns_cloudflare_credentials.ini`
```ini
# Cloudflare API credentials used by Certbot
dns_cloudflare_email = cloudflare@example.com
dns_cloudflare_api_key = 0123456789abcdef0123456789abcdef01234567
```
Contents of secret `certbot_domains.txt`
```txt
example.com
ns.example.com
```
### Full Example: Using Swarm Secrets
This example will sign, deploy certs, reload a target container (requires mounting the `docker.sock`), and email a summary report about the success of those tasks (requires email credential secrets). Four wildcard certificates will be obtained:
- `example.com`, `*.example.com`
- `ns.example.com`, `*.ns.example.com`
- `example2.com`, `*.example2.com`
- `ns.example2.com`, `*.ns.example2.com`
LetsEncrypt expiry notification emails will be sent to: `admin@example.com`
```sh
docker service create --name certbot-dns-cron \
-e STAGING=1 \
-e PLUGIN_DNS_PROVIDER=cloudflare \
-e PLUGIN_DNS_CREDENTIALS_FILE=/run/secrets/certbot_dns_cloudflare_credentials.ini \
-e PLUGIN_DNS_PROPAGATION_SECONDS=10 \
--secret certbot_domains.txt \
--secret certbot_dns_cloudflare_credentials.ini \
-e DOMAIN_ADMIN_EMAIL_LOCALPART=admin
\
-e DEPLOY_CERTS=1 \
\
-e TARGET_CONTAINER_NAME=nginx-proxy_docker-gen \
\
-e EMAIL_REPORT=1 \
--secret certbot_email_from \
--secret certbot_email_to \
--secret certbot_email_user \
--secret certbot_email_password \
--secret certbot_email_smtp_server \
--secret certbot_email_smtp_port \
\
--mount type=bind,source=/path/to/data/certs/,target=/certs \
--mount type=bind,source=/path/to/data/letsencrypt,target=/etc/letsencrypt \
--mount type=bind,source=/var/run/docker.sock,target=/tmp/docker.sock \
--replicas=1 \
theohbrothers/docker-certbot-dns-cron:v1.12.0-cloudflare
```
Contents of secret `certbot_domains.txt`
```txt
example.com
ns.example.com
example2.com
ns.example2.com
```
Contents of secret `certbot_dns_cloudflare_credentials.ini`
```ini
# Cloudflare API credentials used by Certbot
dns_cloudflare_email = cloudflare@example.com
dns_cloudflare_api_key = 0123456789abcdef0123456789abcdef01234567
```
Contents of secret `certbot_email_from`
```txt
me@example.com
```
Contents of secret `certbot_email_to`
```txt
me@example.com
```
Contents of secret `certbot_email_user`
```txt
me@example.com
```
Contents of secret `certbot_email_password`
```txt
myPassword
```
Contents of secret `certbot_email_smtp_server`
```txt
smtp.example.com
```
Contents of secret `certbot_email_smtp_port`
```txt
587
```
## Environment variables
Environment variables are used to configure various stages of the automation process.
### 1. `certbot` Certificate Signing stage
| Name | Default value | Description | Corresponds to `certbot` argument |
|:-------:|:---------------:|:---------:|:---------:|
| `STAGING` | `0` | Whether to use production or staging LetsEncrypt endpoint. 0 for production, 1 for staging
| `RSA_KEY_SIZE` | `4096` | Size of the RSA key. | `--rsa-key-size`
| `DOMAINS` | `""` | Domains (delimited by ';' ) | `--domains`, `-d`
| `DOMAINS_FILE` | `4096` | Same as `DOMAINS`, but this should point to a file. Domains should be delimited by "\n". Useful when using secrets. | `--domains`, `-d`
| `DOMAIN_ADMIN_EMAIL_LOCALPART` | `admin` | Admin Email's Local-part for LetsEncrypt expiry-notification emails. The final email will be `@domain.com` | `--email`, `-m`
| `PLUGIN_DNS_PROVIDER` | `""` | DNS Provider. Valid values are: `cloudflare`, `cloudxns`, `digitalocean`, `dnsimple`, `dnsmadeeasy`, `google`, `linode`, `luadns`, `nsone`, `ovh`, `rfc2136`, `route53` | `--dns-`
| `PLUGIN_DNS_CREDENTIALS_FILE` | `""` | Path to the dns credentials file | `--dns--credentials`.
| `PLUGIN_DNS_PROPAGATION_SECONDS` | certbot plugin default, check plugin documentation | The number of seconds to wait for DNS to propagate before asking the ACME server to verify the DNS record. | `--dns--propagation-seconds`.
### 2. Deploy stage
| Name | Default value | Description |
|:-------:|:---------------:|:---------:|
| `DEPLOY_CERTS` | `""` | Whether to deploy the signed cert, key, fullchain cert, and chain cert. This copies `/etc/letsencrypt/live//privkey.pem` to `/certs/.key`, `/etc/letsencrypt/live//cert.pem` to `/certs/.crt`, `/etc/letsencrypt/live//fullchain.pem` to `/certs/.fullchain.pem`, and `/etc/letsencrypt/live/.chain.pem`. Omit environment variable to disable deploy
### 3. Reload stage
| Name | Default value | Description |
|:-------:|:---------------:|:---------:|
| `TARGET_CONTAINER_NAME` | `""` | Container name to reload (with SIGHUP) after signing and obtaining cert. In Swarm mode, specify `` and any container with name starting with `` will be sent a signal. Only one container name may be matched, so ensure this is as unique as possible. Omit environment variable to disable reload
### 4. Email notification stage
| Name | Default value | Description |
|:-------:|:---------------:|:---------:|
| `EMAIL_REPORT` | `""` | Whether to email the summary report on successful cert-signing, deployment, and reloading of target container. Omit environment variable to disable email
| `EMAIL_FROM` | `""` | Email sender address
| `EMAIL_TO` | `""` | Email receipient address
| `EMAIL_USER` | `""` | SMTP sender account user
| `EMAIL_PASSWORD` | `""` | SMTP sender account password
| `SMTP_SERVER` | `""` | SMTP server DNS / hostname / IP address. E.g. `smtp.example.com`, `1.2.3.4`
| `SMTP_PORT` | `""` | SMTP server port. E.g. `587`, `465`
#### If using Swarm Secrets
Instead of specifying your email credentials in the `docker-stack.yml`, use environment variables suffixed with `_FILE`, each pointing to Swarm Secrets' mountpoints `/run/secrets/`. These files will be read to obtain the email credentials.
| Name | Default value | Description |
|:-------:|:---------------:|:---------:|
| `EMAIL_FROM_FILE` | `/run/secrets/certbot_email_from` | Email sender address
| `EMAIL_TO_FILE` | `/run/secrets/certbot_email_to` | Email receipient address
| `EMAIL_USER_FILE` | `/run/secrets/certbot_email_user` | SMTP sender account user
| `EMAIL_PASSWORD_FILE` | `/run/secrets/certbot_email_password` | SMTP sender account password
| `SMTP_SERVER_FILE` | `/run/secrets/certbot_email_smtp_server` | SMTP server DNS / hostname / IP address. E.g. `smtp.example.com`, `1.2.3.4`
| `SMTP_PORT_FILE` | `/run/secrets/certbot_email_smtp_port` | SMTP server port. E.g. `587`, `465`
## Cron interval
By default, the cron invokes the main script every hour.
## Script usage
### Manually sign a certificate
To do so, invoke the main script, passing domain(s) as arguments.
If a certificate for a given domain doesn't yet exist, a new certificate will be obtained.
If a certificate for a given domain is not due for renewal, certbot shows a message that no renewal is done.
```sh
docker exec -it "$container_name_or_id" sh -c '/app/scripts/signcert-deploy-sendmail.sh example.com'
# For multiple domains
docker exec -it "$container_name_or_id" sh -c '/app/scripts/signcert-deploy-sendmail.sh example.com example2.com example3.com'
```
To force certificate renewal even if the certificate is not yet due for renewal, use the `--force` flag:
```sh
docker exec -it "$container_name_or_id" sh -c '/app/scripts/signcert-deploy-sendmail.sh --force example.com'
# For multiple domains
docker exec -it "$container_name_or_id" sh -c '/app/scripts/signcert-deploy-sendmail.sh --force example.com example2.com example3.com'
```
### Manually deploy a signed certificate
This can either be done by using the provided script `deploy.sh`
```sh
docker exec -it "$container_name_or_id" sh -c '/app/scripts/deploy.sh example.com'
```
### Manually remove a certificate
This can either be done by using the provided script `removecert.sh`, or manually deleting the domain folder in the `letsencrypt` data folder. For `example.com`, delete the folder named `example.com`
```sh
docker exec -it "$container_name_or_id" sh -c '/app/scripts/removecert.sh example.com'
```
### Read a certificate
```sh
docker exec -it "$container_name_or_id" sh -c '/app/scripts/readcert.sh example.com'
```
## Script behaviour
### `certbot` Certificate Signing stage
Assuming you passed in the necessary environment variables, renewing certs would be as simple as invoking the main script, whether through `docker exec`, or directly inside the container. The script reads environment variables each time it is invoked.
### Deploy stage
The script copies each successfully signed domain certificate, key, full chain, and chain certificates to the folder `/certs`.
To disable this stage, omit the environment variable `DEPLOY_CERTS`.
### Reload stage
The script sends a `SIGHUP` (`1`) to a container with name starting with `TARGET_CONTAINER_NAME`.
When `Swarm Mode` is used, all services go by the naming convention ``. `` is the name given when using `docker stack up`, and `` is the service key in the `docker-compose.yml` or `docker-stack.yml`. If a container name starts with ``, ignoring the suffix, that container is sent the signal. As an example, if the value of `TARGET_CONTAINER_NAME` variable is `mystack_docker-gen`, the service called `mystack_docker-gen.1.jb2xwgp3ktnmsmp1eo31563jw` is sent the reload signal. The signal is sent to one container only; if multiple containers names match `mystack_docker-gen`, no signal is sent. Therefore keep the container name as unique as possible.
Mounting the `/var/run/docker.sock` is necessary for reloading to take place.
To disable this stage, omit the environment variable `TARGET_CONTAINER_NAME`.
### Email notification stage
This sends a summarized report of all the previous steps and their success status. Only one email is sent each time the script is invoked.
No email is sent in these cases:
1. The email functionality is disabled by omitting `EMAIL_REPORT`
2. One or more email credentials were not specified, among: `EMAIL_FROM`, `EMAIL_TO`, `EMAIL_USER`, `EMAIL_PASSWORD`, `SMTP_SERVER`, `SMTP_PORT`
3. The email credentials were wrong
4. All the given domains' certificates are not due for renewal
Assuming all variables are set correctly, as long as one certificate is obtained / renewed, a summary report will be sent.
To disable this stage, omit the environment variable `EMAIL_REPORT`.
## Development
Requires Windows `powershell` or [`pwsh`](https://github.com/PowerShell/PowerShell).
```powershell
# Install Generate-DockerImageVariants module: https://github.com/theohbrothers/Generate-DockerImageVariants
Install-Module -Name Generate-DockerImageVariants -Repository PSGallery -Scope CurrentUser -Force -Verbose
# Edit ./generate templates
# Generate the variants
Generate-DockerImageVariants .
```