├── .dockerignore ├── .editorconfig ├── .github ├── dependabot.yml └── workflows │ ├── test.sh │ └── test.yml ├── .gitignore ├── CHANGELOG.md ├── Dockerfile ├── LICENSE ├── Procfile ├── README.md ├── docker-entrypoint.sh ├── docker-gen ├── config │ └── docker-gen.cfg └── templates │ └── Caddyfile.tmpl └── example ├── caddy_global_options └── docker-compose.yml /.dockerignore: -------------------------------------------------------------------------------- 1 | *.md 2 | .* 3 | Dockerfile 4 | docker-compose.yml 5 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Check http://editorconfig.org for more information 2 | # This is the main config file for this project: 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | indent_style = space 8 | trim_trailing_whitespace = true 9 | end_of_line = lf 10 | insert_final_newline = true 11 | indent_size = 2 12 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "02:00" 8 | open-pull-requests-limit: 10 9 | - package-ecosystem: docker 10 | directory: "/" 11 | schedule: 12 | interval: daily 13 | time: "02:00" 14 | open-pull-requests-limit: 10 15 | -------------------------------------------------------------------------------- /.github/workflows/test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -exuo pipefail 3 | 4 | # test a docker process 5 | docker ps | grep -q caddy-gen 6 | 7 | # test availability and status code 8 | curl 127.0.0.1:80 9 | 10 | # test image size < 100MB 11 | (( $(docker inspect caddy-gen:latest -f '{{.Size}}') < 100 * 2**20 )) 12 | 13 | # test feature CADDY_TEMPLATE 14 | printf 'http://test-template.localhost {\n respond "template"\n}\n' > /tmp/template.tmpl 15 | docker cp /tmp/template.tmpl caddy-gen:/tmp/template.tmpl 16 | docker exec caddy-gen sh -c 'export CADDY_TEMPLATE=/tmp/template.tmpl && sh /code/docker-entrypoint.sh' 17 | sleep 2 18 | test "$(curl 0.0.0.0:80 -s -H 'Host: test-template.localhost')" = 'template' 19 | 20 | # test feature CADDY_SNIPPET 21 | printf 'http://test-snippet.localhost {\n respond "snippet"\n}\n' > /tmp/snippet.tmpl 22 | docker cp /tmp/snippet.tmpl caddy-gen:/tmp/snippet.tmpl 23 | docker exec caddy-gen sh \ 24 | -c 'export CADDY_TEMPLATE=/tmp/template.tmpl CADDY_SNIPPET=/tmp/snippet.tmpl && sh /code/docker-entrypoint.sh' 25 | sleep 2 26 | test "$(curl 0.0.0.0:80 -s -H 'Host: test-template.localhost')" = 'template' 27 | test "$(curl 0.0.0.0:80 -s -H 'Host: test-snippet.localhost')" = 'snippet' 28 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | on: 3 | push: 4 | branches: [master] 5 | tags: ['**'] 6 | pull_request: 7 | workflow_dispatch: 8 | concurrency: 9 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 10 | cancel-in-progress: true 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | permissions: 15 | contents: read 16 | packages: write 17 | steps: 18 | - uses: actions/checkout@v4 19 | - uses: docker/setup-buildx-action@v3 20 | - uses: docker/metadata-action@v5 21 | id: meta 22 | with: 23 | images: | 24 | ghcr.io/${{ github.repository }} 25 | wemakeservices/caddy-gen 26 | tags: | 27 | type=ref,event=branch 28 | type=semver,pattern={{version}} 29 | labels: | 30 | org.opencontainers.image.licenses=MIT 31 | - uses: docker/login-action@v3 32 | if: github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags') 33 | with: 34 | registry: ghcr.io 35 | username: ${{ github.actor }} 36 | password: ${{ github.token }} 37 | - uses: docker/login-action@v3 38 | if: github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags') 39 | with: 40 | username: ${{ secrets.DOCKER_USERNAME }} 41 | password: ${{ secrets.DOCKER_PASSWORD }} 42 | - name: docker build 43 | uses: docker/build-push-action@v6 44 | with: 45 | cache-from: type=gha 46 | cache-to: type=gha,mode=max 47 | context: . 48 | load: true 49 | tags: caddy-gen:latest 50 | - name: test 51 | run: | 52 | docker run -d -p 80:80 -v /var/run/docker.sock:/tmp/docker.sock:ro --name caddy-gen caddy-gen:latest 53 | sleep 5 # wait for container to start 54 | ./.github/workflows/test.sh 55 | - name: docker push 56 | uses: docker/build-push-action@v6 57 | with: 58 | cache-from: type=gha 59 | cache-to: type=gha,mode=max 60 | context: . 61 | push: ${{ github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags') }} 62 | tags: ${{ steps.meta.outputs.tags }} 63 | labels: ${{ steps.meta.outputs.labels }} 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | caddy-info 2 | 3 | #### joe made this: http://goel.io/joe 4 | #### macos #### 5 | # General 6 | *.DS_Store 7 | .AppleDouble 8 | .LSOverride 9 | 10 | # Icon must end with two \r 11 | Icon 12 | 13 | 14 | # Thumbnails 15 | ._* 16 | 17 | # Files that might appear in the root of a volume 18 | .DocumentRevisions-V100 19 | .fseventsd 20 | .Spotlight-V100 21 | .TemporaryItems 22 | .Trashes 23 | .VolumeIcon.icns 24 | .com.apple.timemachine.donotpresent 25 | 26 | # Directories potentially created on remote AFP share 27 | .AppleDB 28 | .AppleDesktop 29 | Network Trash Folder 30 | Temporary Items 31 | .apdisk 32 | #### windows #### 33 | # Windows thumbnail cache files 34 | Thumbs.db 35 | ehthumbs.db 36 | ehthumbs_vista.db 37 | 38 | # Dump file 39 | *.stackdump 40 | 41 | # Folder config file 42 | Desktop.ini 43 | 44 | # Recycle Bin used on file shares 45 | $RECYCLE.BIN/ 46 | 47 | # Windows Installer files 48 | *.cab 49 | *.msi 50 | *.msm 51 | *.msp 52 | 53 | # Windows shortcuts 54 | *.lnk 55 | #### linux #### 56 | *~ 57 | 58 | # temporary files which can be created if a process still has a handle open of a deleted file 59 | .fuse_hidden* 60 | 61 | # KDE directory preferences 62 | .directory 63 | 64 | # Linux trash folder which might appear on any partition or disk 65 | .Trash-* 66 | 67 | # .nfs files are created when an open file is removed but is still being accessed 68 | .nfs* 69 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Version history 2 | 3 | We follow Semantic Versions since the `0.1.0` release. 4 | We used to have incremental versioning before `0.1.0`. 5 | 6 | 7 | ## Version 0.3.0 8 | 9 | - Updates `alpine` version to `3.11` 10 | - Adds `http` basic auth 11 | - Adds `docker-image-size-limit` 12 | 13 | 14 | ## Version 0.2.0 15 | 16 | - Updates `alpine` version to `3.10` 17 | - Updates `Caddy` version to `0.10.12` 18 | - Adds `"virtual.websockets"` configuration to work with websockets 19 | - Adds `docker-image-size-limit` 20 | - Minifies image as possible 21 | 22 | 23 | ## Version 0.1.0 24 | 25 | - Updates `alpine` version to `3.7` 26 | - Updates `Caddy` version 27 | - Updates `DockerGen` version 28 | - Adds new labels, including: `vendor` and `version` 29 | 30 | 31 | ## Version 0.0.2 32 | 33 | - Add optional domain alias, so alias will redirect to the main host 34 | 35 | 36 | ## Version 0.0.1 37 | 38 | - Initial release 39 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM caddy:2.10.0-alpine 2 | 3 | ARG DOCKER_GEN_VERSION="0.14.0" 4 | ARG FOREGO_VERSION="0.16.1" 5 | 6 | ENV CADDYPATH="/etc/caddy" 7 | ENV DOCKER_HOST="unix:///tmp/docker.sock" 8 | 9 | # Install all dependenices: 10 | RUN apk update && apk upgrade \ 11 | && apk add --no-cache bash openssh-client git \ 12 | && apk add --no-cache --virtual .build-dependencies curl wget tar \ 13 | # Install Forego 14 | && wget --quiet "https://github.com/jwilder/forego/releases/download/v${FOREGO_VERSION}/forego" \ 15 | && mv ./forego /usr/bin/forego \ 16 | && chmod u+x /usr/bin/forego \ 17 | # Install docker-gen 18 | && wget --quiet "https://github.com/nginx-proxy/docker-gen/releases/download/${DOCKER_GEN_VERSION}/docker-gen-alpine-linux-amd64-${DOCKER_GEN_VERSION}.tar.gz" \ 19 | && tar -C /usr/bin -xvzf "docker-gen-alpine-linux-amd64-${DOCKER_GEN_VERSION}.tar.gz" \ 20 | && rm "docker-gen-alpine-linux-amd64-${DOCKER_GEN_VERSION}.tar.gz" \ 21 | && apk del .build-dependencies 22 | 23 | EXPOSE 80 443 2015 24 | VOLUME /etc/caddy 25 | 26 | # Starting app: 27 | COPY . /code 28 | COPY ./docker-gen/templates/Caddyfile.tmpl /code/docker-gen/templates/Caddyfile.bkp 29 | WORKDIR /code 30 | 31 | ENTRYPOINT ["sh", "/code/docker-entrypoint.sh"] 32 | CMD ["/usr/bin/forego", "start", "-r"] 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 wemake.services 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | dockergen: docker-gen -watch -config /code/docker-gen/config/docker-gen.cfg 2 | caddy: caddy start --config /etc/caddy/Caddyfile --watch 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # caddy-gen 2 | 3 | [![wemake.services](https://img.shields.io/badge/-wemake.services-green.svg?label=%20&logo=data%3Aimage%2Fpng%3Bbase64%2CiVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAABGdBTUEAALGPC%2FxhBQAAAAFzUkdCAK7OHOkAAAAbUExURQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP%2F%2F%2F5TvxDIAAAAIdFJOUwAjRA8xXANAL%2Bv0SAAAADNJREFUGNNjYCAIOJjRBdBFWMkVQeGzcHAwksJnAPPZGOGAASzPzAEHEGVsLExQwE7YswCb7AFZSF3bbAAAAABJRU5ErkJggg%3D%3D)](https://wemake.services) 4 | [![test](https://github.com/wemake-services/caddy-gen/actions/workflows/test.yml/badge.svg?event=push)](https://github.com/wemake-services/caddy-gen/actions/workflows/test.yml) 5 | [![Dockerhub](https://img.shields.io/docker/pulls/wemakeservices/caddy-gen.svg)](https://hub.docker.com/r/wemakeservices/caddy-gen/) 6 | 7 | A perfect mix of [`Caddy`](https://github.com/mholt/caddy), [`docker-gen`](https://github.com/jwilder/docker-gen), and [`forego`](https://github.com/jwilder/forego). Inspired by [`nginx-proxy`](https://github.com/jwilder/nginx-proxy). 8 | 9 | Download: 10 | - https://hub.docker.com/r/wemakeservices/caddy-gen 11 | - https://ghcr.io/wemake-services/caddy-gen 12 | 13 | --- 14 | 15 | ## Why 16 | 17 | Using `Caddy` as your primary web server is super simple. 18 | But when you need to scale your application Caddy is limited to its static configuration. 19 | 20 | To overcome this issue we are using `docker-gen` to generate configuration everytime a container spawns or dies. 21 | Now scaling is easy! 22 | 23 | ## Configuration / Options 24 | 25 | `caddy-gen` is configured with [`labels`](https://docs.docker.com/engine/userguide/labels-custom-metadata/). 26 | 27 | The main idea is simple. 28 | Every labeled service exposes a `virtual.host` to be handled. 29 | Then, every container represents a single `upstream` to serve requests. 30 | 31 | NOTE: Caddy2 was introduced in [version 0.3.0](https://github.com/wemake-services/caddy-gen/releases/tag/0.3.0) causing BREAKING CHANGES. 32 | 33 | Main configuration options: 34 | 35 | - `virtual.host` (required) domain name, don't pass `http://` or `https://`, you can separate them with spaces. 36 | - `virtual.alias` domain alias, useful for `www` prefix with redirect. For example `www.myapp.com`. Alias will always redirect to the host above. 37 | - `virtual.port` port exposed by container, e.g. `3000` for React apps in development. 38 | - `virtual.tls-email` the email address to use for the ACME account managing the site's certificates (required to enable HTTPS). 39 | - `virtual.tls` alias of `virtual.tls-email`. 40 | - `virtual.host.directives` set custom [Caddyfile directives](https://caddyserver.com/docs/caddyfile/directives) for the host. These will be inlined into the site block. 41 | - `virtual.host.import` include Caddyfile directives for the host from a file on the container's filesystem. See [Caddy import](https://caddyserver.com/docs/caddyfile/directives/import). 42 | 43 | [Basic authentication](https://caddyserver.com/docs/caddyfile/directives/basicauth) options: 44 | - `virtual.auth.path` with 45 | - `virtual.auth.username` and 46 | - `virtual.auth.password` together enable HTTP basic authentication. (Password should be a string `base64` encoded from `bcrypt` hash. You can use https://bcrypt-generator.com/ with default config and https://www.base64encode.org/.) 47 | 48 | [Reverse proxy](https://caddyserver.com/docs/caddyfile/directives/reverse_proxy) options: 49 | - `virtual.proxy.matcher` have the reverse proxy only match certain paths. 50 | - `virtual.proxy.lb_policy` specify load balancer policy, defaults to `round_robin`. 51 | - `virtual.proxy.directives` include any reverse_proxy directives. These will be inlined into the reverse proxy block. 52 | - `virtual.proxy.import` include any reverse_proxy directives from a file on the container's filesystem. See [Caddy import](https://caddyserver.com/docs/caddyfile/directives/import). 53 | 54 | To include a custom template: 55 | - mount a volume containing your custom template and/or snippet (they both may 56 | be Go templates and will be loaded by `docker-gen`). 57 | - set the environment variable `CADDY_TEMPLATE` to the mounted file containining 58 | your custom Caddyfile template. This will replace the default template. 59 | - set the environment variable `CADDY_SNIPPET` to the mounted file containining 60 | your custom Caddyfile snippet. This will be prepended to the caddy template, 61 | so you may use it to set [Global Options](https://caddyserver.com/docs/caddyfile/options), 62 | define [snippet blocks](https://caddyserver.com/docs/caddyfile/concepts#snippets), 63 | or [add custom address blocks](https://caddyserver.com/docs/caddyfile/concepts). 64 | - See [example "Use a custom Caddy template for `docker-gen`"](#use-a-custom-caddy-template-for-docker-gen) 65 | 66 | ### Version build-time arguments 67 | 68 | This image supports two [build-time](https://docs.docker.com/engine/reference/commandline/build/#set-build-time-variables-build-arg) arguments: 69 | 70 | - `FOREGO_VERSION` to change the current version of [`forego`](https://github.com/jwilder/forego/releases) 71 | - `DOCKER_GEN_VERSION` to change the current version of [`docker-gen`](https://github.com/jwilder/docker-gen/releases) 72 | 73 | ## Usage 74 | 75 | Caddy-gen is created to be used in a single container. It will act as a reverse 76 | proxy for the whoami service. 77 | 78 | ```yaml 79 | version: "3" 80 | services: 81 | caddy-gen: 82 | container_name: caddy-gen 83 | image: wemakeservices/caddy-gen:latest # or ghcr.io/wemake-services/caddy-gen:latest 84 | restart: always 85 | volumes: 86 | - /var/run/docker.sock:/tmp/docker.sock:ro # needs socket to read events 87 | - ./caddy-info:/data/caddy # needs volume to back up certificates 88 | ports: 89 | - "80:80" 90 | - "443:443" 91 | depends_on: 92 | - whoami 93 | 94 | whoami: # this is your service 95 | image: "katacoda/docker-http-server:v2" 96 | labels: 97 | - "virtual.host=myapp.com" # your domain 98 | - "virtual.alias=www.myapp.com" # alias for your domain (optional) 99 | - "virtual.port=80" # exposed port of this container 100 | - "virtual.tls-email=admin@myapp.com" # ssl is now on 101 | - "virtual.auth.path=/secret/*" # path basic authentication applies to 102 | - "virtual.auth.username=admin" # Optionally add http basic authentication 103 | - "virtual.auth.password=JDJ5JDEyJEJCdzJYM0pZaWtMUTR4UVBjTnRoUmVJeXQuOC84QTdMNi9ONnNlbDVRcHltbjV3ME1pd2pLCg==" # By specifying both username and password hash 104 | ``` 105 | 106 | See [`docker-compose.yml`](https://github.com/wemake-services/caddy-gen/blob/master/example/docker-compose.yml) example file. 107 | 108 | > [!NOTE] 109 | > Literal `$` should be doubled (`$$`) to avoid [docker compose interpolation](https://docs.docker.com/reference/compose-file/interpolation), e.g.: 110 | > ```yaml 111 | > labels: 112 | > virtual.host.directives: | 113 | > basic_auth { 114 | > usr $2a$14$aSp4Ch... # will fail 115 | > usr $2a$14$$aSp4Ch... # works 116 | > } 117 | > ``` 118 | 119 | ### Backing up certificates 120 | 121 | To backup certificates make a volume: 122 | 123 | ```yaml 124 | services: 125 | caddy: 126 | volumes: 127 | - ./caddy-info:/data/caddy 128 | ``` 129 | 130 | ### Add or modify reverse_proxy headers 131 | 132 | With the following settings, the upstream host will see its own address instead 133 | of the original incoming value. See [Headers](https://caddyserver.com/docs/caddyfile/directives/reverse_proxy#headers). 134 | 135 | ```yaml 136 | version: "3" 137 | services: 138 | caddy-gen: 139 | image: wemakeservices/caddy-gen:latest # or ghcr.io/wemake-services/caddy-gen:latest 140 | restart: always 141 | volumes: 142 | - /var/run/docker.sock:/tmp/docker.sock:ro # needs socket to read events 143 | - ./caddy-info:/data/caddy # needs volume to back up certificates 144 | ports: 145 | - "80:80" 146 | - "443:443" 147 | depends_on: 148 | - whoami 149 | 150 | whoami: 151 | image: "katacoda/docker-http-server:v2" 152 | labels: 153 | virtual.host: myapp.com 154 | virtual.port: 80 155 | virtual.tls: admin@myapp.com 156 | virtual.proxy.directives: | 157 | header_up Host {http.reverse_proxy.upstream.hostport} 158 | ``` 159 | 160 | ### Set up a static file server for a host 161 | 162 | With the following settings, myapp.com will serve files from directory `www` 163 | and only requests to `/api/*` will be routed to the whoami service. See 164 | [file_server](https://caddyserver.com/docs/caddyfile/directives/file_server). 165 | 166 | ```yaml 167 | version: "3" 168 | services: 169 | caddy-gen: 170 | image: wemakeservices/caddy-gen:latest # or ghcr.io/wemake-services/caddy-gen:latest 171 | restart: always 172 | volumes: 173 | - /var/run/docker.sock:/tmp/docker.sock:ro # needs socket to read events 174 | - ./caddy-info:/data/caddy # needs volume to back up certificates 175 | - ./www:/srv/myapp/www # files served by myapp.com 176 | ports: 177 | - "80:80" 178 | - "443:443" 179 | depends_on: 180 | - whoami 181 | 182 | whoami: 183 | image: "katacoda/docker-http-server:v2" 184 | labels: 185 | virtual.host: myapp.com 186 | virtual.port: 80 187 | virtual.tls: admin@myapp.com 188 | virtual.proxy.matcher: /api/* 189 | virtual.host.directives: | 190 | root * /srv/myapp/www 191 | templates 192 | file_server 193 | ``` 194 | 195 | ### Use a custom Caddy template for `docker-gen` 196 | 197 | With this custom template, Caddy-gen will act as a reverse proxy for service 198 | containers and store their logs under the appropriate host folder in 199 | `/var/logs`. 200 | 201 | ```caddy 202 | # file: ./caddy/template 203 | (redirectHttps) { 204 | @http { 205 | protocol http 206 | } 207 | redir @http https://{host}{uri} 208 | } 209 | 210 | (logFile) { 211 | log { 212 | output file /var/caddy/{host}/logs { 213 | roll_keep_for 7 214 | } 215 | } 216 | } 217 | 218 | {{ $hosts := groupByLabel $ "virtual.host" }} 219 | {{ range $h, $containers := $hosts }} 220 | {{ range $t, $host := split (trim (index $c.Labels "virtual.host")) " " }} 221 | {{ $tls = trim (index $c.Labels "virtual.tls") }} 222 | {{ $host }} { 223 | {{ if $tls }} 224 | tls {{ $tls }} 225 | import redirectHttps 226 | {{ end }} 227 | reverse_proxy { 228 | lb_policy round_robin 229 | {{ range $i, $container := $containers }} 230 | {{ range $j, $net := $container.Networks }} 231 | to {{ $net.IP}}:{{ or (trim (index $container.Labels "virtual.port")) "80" }} 232 | {{ end }} 233 | {{ end }} 234 | } 235 | encode zstd gzip 236 | import logFile 237 | } 238 | ``` 239 | 240 | ```yaml 241 | # file: docker-compose.yml 242 | services: 243 | caddy-gen: 244 | volumes: 245 | # mount the template file into the container 246 | - ./caddy/template:/tmp/caddy/template 247 | environment: 248 | # CADDY_TEMPLATE will replace the default caddy template 249 | CADDY_TEMPLATE: /tmp/caddy/template 250 | ``` 251 | 252 | ### Set [global options](https://caddyserver.com/docs/caddyfile/options) for Caddy 253 | 254 | With this snippet, Caddy will request SSL certificates from the [Let's Encrypt 255 | staging environment](https://letsencrypt.org/docs/staging-environment/). This 256 | is [useful for testing](https://caddyserver.com/docs/automatic-https#testing) 257 | without running up against rate limits when you want to deploy. 258 | 259 | ```caddy 260 | # file: ./caddy/global_options 261 | { 262 | acme_ca https://acme-staging-v02.api.letsencrypt.org/directory 263 | } 264 | ``` 265 | 266 | ```yaml 267 | # file: docker-compose.yml 268 | services: 269 | caddy-gen: 270 | volumes: 271 | # mount the template file into the container 272 | - ./caddy/global_options:/tmp/caddy/global_options 273 | environment: 274 | # CADDY_SNIPPET will prepend to the default caddy template 275 | CADDY_SNIPPET: /tmp/caddy/global_options 276 | ``` 277 | 278 | ## See also 279 | 280 | - Raw `Caddy` [image](https://github.com/wemake-services/caddy-docker) 281 | - [Django project template](https://github.com/wemake-services/wemake-django-template) with `Caddy` 282 | - Tool to limit your `docker` [image size](https://github.com/wemake-services/docker-image-size-limit) 283 | 284 | ## Changelog 285 | 286 | Full changelog is available [here](https://github.com/wemake-services/caddy-gen/blob/master/CHANGELOG.md). 287 | 288 | ## License 289 | 290 | MIT. See [LICENSE](https://github.com/wemake-services/caddy-gen/blob/master/LICENSE) for more details. 291 | -------------------------------------------------------------------------------- /docker-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -o errexit 4 | set -o nounset 5 | 6 | CADDY_SNIPPET="${CADDY_SNIPPET:-}" 7 | # caddy_template is by default set to the backup to avoid adding the snippet 8 | # multiple times to the same template, for example when a container restarts 9 | CADDY_TEMPLATE="${CADDY_TEMPLATE:-./docker-gen/templates/Caddyfile.bkp}" 10 | 11 | # Create template file 12 | truncate -s 0 /code/docker-gen/templates/Caddyfile.tmpl 13 | if [ -n "$CADDY_SNIPPET" ]; then 14 | cat "$CADDY_SNIPPET" >> /code/docker-gen/templates/Caddyfile.tmpl 15 | fi 16 | cat "$CADDY_TEMPLATE" >> /code/docker-gen/templates/Caddyfile.tmpl 17 | 18 | # Create initial configuration: 19 | docker-gen /code/docker-gen/templates/Caddyfile.tmpl /etc/caddy/Caddyfile 20 | 21 | # Execute passed command: 22 | exec "$@" 23 | -------------------------------------------------------------------------------- /docker-gen/config/docker-gen.cfg: -------------------------------------------------------------------------------- 1 | [[config]] 2 | template = "/code/docker-gen/templates/Caddyfile.tmpl" 3 | dest = "/etc/caddy/Caddyfile" 4 | onlyexposed = false 5 | watch = true 6 | wait = "500ms:2s" -------------------------------------------------------------------------------- /docker-gen/templates/Caddyfile.tmpl: -------------------------------------------------------------------------------- 1 | {{ $hosts := groupByLabel $ "virtual.host" }} 2 | 3 | {{ if not $hosts }} 4 | 5 | 127.0.0.1:2015 { 6 | log { 7 | output stdout 8 | } 9 | } 10 | 11 | 12 | {{ else }} 13 | 14 | {{ range $h, $containers := $hosts }} 15 | {{ $c := first $containers }} 16 | {{ $allhosts := trim (index $c.Labels "virtual.host") }} 17 | {{ range $t, $host := split $allhosts " " }} 18 | {{ $tlsEmail := trim (index $c.Labels "virtual.tls-email") }} 19 | {{ $tlsConfig := trim (index $c.Labels "virtual.tls") }} 20 | {{ $tlsEnv := or $tlsEmail $tlsConfig }} 21 | {{ $tlsOff := eq $tlsEnv "" }} 22 | {{ $tlsOn := ne $tlsEnv "" }} 23 | {{ $alias := trim (index $c.Labels "virtual.alias") }} 24 | {{ $aliasPresent := ne $alias "" }} 25 | {{ $authUsername := trim (index $c.Labels "virtual.auth.username") }} 26 | {{ $authPassword := trim (index $c.Labels "virtual.auth.password") }} 27 | {{ $authPath := trim (index $c.Labels "virtual.auth.path") }} 28 | {{ $basicauth := and (ne $authUsername "") (ne $authPassword "") }} 29 | {{ $hostDirectives := trim (index $c.Labels "virtual.host.directives") }} 30 | {{ $hostImport := trim (index $c.Labels "virtual.host.import") }} 31 | {{ $proxyMatcher := trim (index $c.Labels "virtual.proxy.matcher") }} 32 | {{ $proxyDirectives := trim (index $c.Labels "virtual.proxy.directives") }} 33 | {{ $proxyLBPolicy := or (trim (index $c.Labels "virtual.proxy.lb_policy")) "round_robin" }} 34 | {{ $proxyImport := trim (index $c.Labels "virtual.proxy.import") }} 35 | 36 | {{ if $aliasPresent }} 37 | {{ if $tlsOff }}http://{{ end }}{{ $alias }} { 38 | redir {{ if $tlsOff }}http://{{ else }}https://{{ end }}{{ $host }} 39 | } 40 | {{ end }} 41 | 42 | {{ if $tlsOff }}http://{{ end }}{{ $host }} { 43 | {{ if $tlsOn }}tls {{ $tlsEnv }}{{ end }} 44 | 45 | {{ $hostDirectives }} 46 | {{ if $hostImport }}import {{ $hostImport }}{{ end }} 47 | 48 | {{ if $basicauth }} 49 | basic_auth {{ $authPath }} { 50 | {{ $authUsername }} {{ $authPassword }} 51 | } 52 | {{ end }} 53 | 54 | reverse_proxy {{ $proxyMatcher }} { 55 | lb_policy {{ $proxyLBPolicy }} 56 | {{ $proxyDirectives }} 57 | {{ if $proxyImport }}import {{ $proxyImport }}{{ end }} 58 | {{ range $i, $container := $containers }} 59 | {{ range $j, $net := $container.Networks }} 60 | {{ $port := or (trim (index $container.Labels "virtual.port")) "80" }} 61 | to {{ $net.IP }}:{{ $port }} 62 | {{ end }} 63 | {{ end }} 64 | } 65 | 66 | encode zstd gzip 67 | log { 68 | output stdout 69 | } 70 | } 71 | {{ end }} 72 | {{ end }} 73 | 74 | {{ end }} 75 | -------------------------------------------------------------------------------- /example/caddy_global_options: -------------------------------------------------------------------------------- 1 | { 2 | acme_ca https://acme-staging-v02.api.letsencrypt.org/directory 3 | } 4 | -------------------------------------------------------------------------------- /example/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.8" 2 | services: 3 | caddy-gen: 4 | container_name: caddy2-gen 5 | build: .. 6 | restart: always 7 | volumes: 8 | - /var/run/docker.sock:/tmp/docker.sock:ro 9 | - ./caddy-info:/data/caddy 10 | - ./caddy_global_options:/tmp/caddy_global_options 11 | ports: 12 | - "80:80" 13 | - "443:443" 14 | environment: 15 | CADDY_SNIPPET: /tmp/caddy_global_options 16 | depends_on: 17 | - whoami 18 | - whoami2 19 | 20 | whoami: 21 | image: "katacoda/docker-http-server:v1" 22 | labels: 23 | - "virtual.host=test1.localhost" 24 | 25 | whoami2: 26 | image: "katacoda/docker-http-server:v2" 27 | labels: 28 | - "virtual.host=myapp.com" # your domain 29 | - "virtual.alias=www.myapp.com" # alias for your domain (optional) 30 | - "virtual.port=80" # exposed port of this container 31 | - "virtual.tls-email=admin@myapp.com" # ssl is now on 32 | --------------------------------------------------------------------------------