{"id":16179188,"url":"https://github.com/jonashackt/traefik-cache-nginx-spring-boot","last_synced_at":"2025-03-16T10:31:38.978Z","repository":{"id":41067133,"uuid":"125065067","full_name":"jonashackt/traefik-cache-nginx-spring-boot","owner":"jonashackt","description":"Example project showing how to use Nginx as a simple cache for Traefik - incl. a Spring Boot client and server","archived":false,"fork":false,"pushed_at":"2025-03-05T03:18:37.000Z","size":1083,"stargazers_count":21,"open_issues_count":9,"forks_count":9,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-03-05T04:23:47.796Z","etag":null,"topics":["caching","docker-compose-rule","docker-network","nginx","rest-template","spring-boot","traefik"],"latest_commit_sha":null,"homepage":null,"language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jonashackt.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-03-13T14:33:16.000Z","updated_at":"2025-03-05T03:18:26.000Z","dependencies_parsed_at":"2023-10-03T04:41:16.894Z","dependency_job_id":"2d54343a-8add-4e15-8771-7fb89a842e49","html_url":"https://github.com/jonashackt/traefik-cache-nginx-spring-boot","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonashackt%2Ftraefik-cache-nginx-spring-boot","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonashackt%2Ftraefik-cache-nginx-spring-boot/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonashackt%2Ftraefik-cache-nginx-spring-boot/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonashackt%2Ftraefik-cache-nginx-spring-boot/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jonashackt","download_url":"https://codeload.github.com/jonashackt/traefik-cache-nginx-spring-boot/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243814895,"owners_count":20352036,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["caching","docker-compose-rule","docker-network","nginx","rest-template","spring-boot","traefik"],"created_at":"2024-10-10T05:25:50.873Z","updated_at":"2025-03-16T10:31:38.692Z","avatar_url":"https://github.com/jonashackt.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"traefik-cache-nginx-spring-boot \n=============================\n[![Build Status](https://github.com/jonashackt/traefik-cache-nginx-spring-boot/workflows/github/badge.svg)](https://github.com/jonashackt/traefik-cache-nginx-spring-boot/actions)\n[![License](http://img.shields.io/:license-mit-blue.svg)](https://github.com/jonashackt/traefik-cache-nginx-spring-boot/blob/master/LICENSE)\n[![renovateenabled](https://img.shields.io/badge/renovate-enabled-yellow)](https://renovatebot.com)\n[![versionspringboot](https://img.shields.io/badge/dynamic/xml?color=brightgreen\u0026url=https://raw.githubusercontent.com/jonashackt/traefik-cache-nginx-spring-boot/master/pom.xml\u0026query=%2F%2A%5Blocal-name%28%29%3D%27project%27%5D%2F%2A%5Blocal-name%28%29%3D%27parent%27%5D%2F%2A%5Blocal-name%28%29%3D%27version%27%5D\u0026label=springboot)](https://github.com/spring-projects/spring-boot)\n[![versiontestcontainers](https://img.shields.io/badge/dynamic/xml?color=brightgreen\u0026url=https://raw.githubusercontent.com/jonashackt/traefik-cache-nginx-spring-boot/master/docker-network-client/pom.xml\u0026query=%2F%2A%5Blocal-name%28%29%3D%27project%27%5D%2F%2A%5Blocal-name%28%29%3D%27properties%27%5D%2F%2A%5Blocal-name%28%29%3D%27testcontainers.version%27%5D\u0026label=testcontainers)](https://www.testcontainers.org/)\n\nAs [Traefik](https://traefik.io/) is a really gread \u0026 modern loadbalancer, but it sadly [doesn´t feature caching right now](https://github.com/containous/traefik/issues/878). So we need to put something in front of it, that is able to do caching - like old [Nginx](https://nginx.org/en/).\n\nAnd as I like full examples, let´s bring in a client application (weatherclient), which want´s to call a server backend (weatherbackend). Both are implemented as simple Spring Boot microservices, as the following ASCII shows: \n\n```\n                  -----------------------------------------------------------------------------    \n                 | Docker Network scope                                                        |  \n                 |                                                                             |  \n                 |                                                                             |   \n                 |                                                                             |\n ============    |   ==============     ================     ===============     ============  |\n =  docker- =    |   =            =     =              =     =             =     =          =  |\n = network- = -----\u003e =   weather  = --\u003e =    Nginx     = --\u003e =   Traefik   = --\u003e =  weather =  |\n =  client  =    |   =    client  =     =  (caching)   =     = (loadbalan.)=     =  backend =  |\n ============    |   ==============     ================     ===============     ============  |\n                 |                                                                             |\n                 |                                                                             |\n                 |                                                                             |\n                  -----------------------------------------------------------------------------\n                 \n```\n\nIt also shows, that we simulate the whole scenario with [Docker](https://www.docker.com/). To have the chance to execute everything within an intergration test, we use [docker-compose-rule](https://github.com/palantir/docker-compose-rule) and the docker-network-client app. Why?\n\nAs the weatherclient only has access to the DNS alias `weatherbackend`, if it itself is part of the Docker (Compose) network, we need another way to run an Integration test inside the Docker network scope. Therefore we use the [docker-compose-rule](https://github.com/palantir/docker-compose-rule) and the __docker-network-client__ that just calls __weatherclient__ inside the Docker network.\n\n## HowTo Use\n\nEverything you need to run a full build and __complete__ test (incl. Integrationtest of docker-network-client firing up all microservices that´ll call each other with client certificate support) is this:\n\n```\nmvn clean install\n```\n\nOnly if you want to check everything manually - which you for sure want to do :) - fire up all components with:\n\n```\ndocker-compose up -d\n```\n\nNow you can have a look at some of the components of our architecture:\n\nTraefik: http://localhost:8080/dashboard/#/\n\nweatherclient: http://localhost:8085/swagger-ui.html\n\nNginx: http://localhost:8088/\n\n\n#### Alternative variant without Docker-Compose for the Dockerized Spring Boot backends\n\nIn a real world DevOps scenario, you may want to run Traefik separate from the Dockerized Spring Boot backends - so that each Backend is able to have it's own CI/CD pipeline for example (e.g. GitLab CI).\n\nNow in this case you don't want a central `docker-compose.yml`, since you want to run and scale all the services independently.\n\nThere's a way to run Traefik and the backends separately, as shown in the [docker-compose-traefik-only.yml](docker-compose-traefik-only.yml) [without-compose.sh](without-compose.sh) files:\n\n```yaml\nversion: '3.8'\n\nservices:\n\n  traefik:\n    image: traefik\n    command:\n      # - \"--logLevel=DEBUG\"\n      - \"--api.insecure=true\"\n      # Enabling docker provider\n      - \"--providers.docker=true\"\n      # Do not expose containers unless explicitly told so\n      - \"--providers.docker.exposedbydefault=false\"\n      - \"--entrypoints.web.address=:80\"\n    ports:\n      - \"80:80\"\n      - \"8080:8080\"\n    networks:\n      - traefiknet\n    volumes:\n      - \"/var/run/docker.sock:/var/run/docker.sock:ro\"\n    restart:\n      always\n\nnetworks:\n  traefiknet:\n```\n\n\n```shell script\n#!/usr/bin/env bash\n\necho \"[--\u003e] Start Traefik using Compose as usual\"\ndocker-compose -f docker-compose-traefik-only.yml up -d\n\necho \"[--\u003e] Startup weatherbackend without Compose - but connect to Traefik\"\ndocker build ./weatherbackend --tag weatherbackend\ndocker run \\\n  --rm \\\n  -d \\\n  -p 8095 \\\n  --label=\"traefik.enable=true\" \\\n  --label=\"traefik.http.routers.whoami.entrypoints=web\" \\\n  --label=\"traefik.http.routers.weatherbackend.rule=Host(\\`weatherbackend.server.test\\`)\" \\\n  --network=\"traefik-cache-nginx-spring-boot_traefiknet\" \\\n  --name weatherbackend \\\n  weatherbackend\n\necho \"[--\u003e] Startup weatherclient without Compose - but connect to Traefik\"\ndocker build ./weatherclient --tag weatherclient\ndocker run \\\n  --rm \\\n  -d \\\n  -p 8085:8085 \\\n  --network=\"traefik-cache-nginx-spring-boot_traefiknet\" \\\n  --name weatherclient \\\n  weatherclient\n```\n\nThe downside of this approach is, that one cannot use the scaling option of Docker-Compose now so easily.\n\n\n## Nginx + Traefik + weatherbackend in logical scope (aka host) with the help of Docker DNS\n\nAdditionally, in real world scenarios, Nginx + Traefik + weatherbackend would reside on a separate host with their own DNS configuration. So there´s a second \"logical\" scope here, which we could have implemented with tools like Vagrant - but this would have been overkill here.\n\nTrying to imitate a machine, where Traefik + weatherbackend + Nginx are running all on one machine with DNS configured, we configure the [Docker DNS alias](https://docs.docker.com/v17.09/engine/userguide/networking/configure-dns/) for weatherbackend to the Traefik container, which routes it then to the weatherbackend. This is done with the help of this Docker Compose configuration ([see the docs](https://docs.docker.com/compose/compose-file/#aliases)):\n\n```\n  traefik:\n    ...\n    networks:\n      default:\n        aliases:\n          - weatherbackend.server.test\n    ...\n```\n\nInstead of configuring the weatherbackend directly to have the DNS alias `weatherbackend.server.test`, we use the Traefik Docker Compose service here - which has a similar effect to a scoped machine around Nginx, Traefik \u0026 weatherbackend. We should see the call now in Traefik GUI:\n\n![first-call-weatherbackend-through-traefik-with-docker-dns-configured](first-call-weatherbackend-through-traefik-with-docker-dns-configured.png)\n\n## Now installing Nginx as cache\n\nTo see what´s happening, we need to activate Nginx´ logging to the Docker log system. As we use the alpine image here, we need to [create our own Dockerfile for that](https://stackoverflow.com/a/42369571/4964553):\n\n```\nFROM nginx:alpine\n\n# forward request and error logs to docker log collector\nRUN ln -sf /dev/stdout /var/log/nginx/access.log \\\n    \u0026\u0026 ln -sf /dev/stderr /var/log/nginx/error.log\n\nCMD [\"nginx-debug\", \"-g\", \"daemon off;\"]\n```\n\nAdditionally, we need to imitate the Scope of Nginx + Traefik + weatherbackend again - now that Nginx is at front of all three:\n\n```\n  nginx:\n    build: ./nginx\n    ...\n    ...\n    networks:\n      default:\n        aliases:\n          - weatherbackend.server.test\n    ...\n```\n\nNow, the [WeatherclientController.class](https://github.com/jonashackt/traefik-cache-nginx-spring-boot/blob/master/weatherclient/src/main/java/de/jonashackt/controller/WeatherclientController.java) needs to access Nginx instead of Traefik directly. This is done with the like the [first tutorial steps with Traefik discribe](https://blog.codecentric.de/en/2017/09/traefik-modern-reverse-proxy/) - like the famous curl:\n\n```\ncurl -H Host:weatherbackend.server.test http://nginx:80 -v\n```\n\nWe therefore set the `Host` header when using Spring´s RestTemplate:\n\n```\nimport org.springframework.http.*;\nimport org.springframework.web.bind.annotation.GetMapping;\nimport org.springframework.web.bind.annotation.PathVariable;\nimport org.springframework.web.bind.annotation.ResponseStatus;\nimport org.springframework.web.bind.annotation.RestController;\nimport org.springframework.web.client.RestTemplate;\n\nimport javax.annotation.PostConstruct;\n\n@RestController\npublic class WeatherclientController {\n\n    private RestTemplate restTemplate = new RestTemplate();\n\n    /*\n     * Without the System property, we won´t be able to set the Host header, see\n     * https://stackoverflow.com/questions/43223261/setting-host-header-for-spring-resttemplate-doesnt-work/43224279\n     * and https://stackoverflow.com/a/8172736/4964553\n     */\n    @PostConstruct\n    public void setProperty() {\n        System.setProperty(\"sun.net.http.allowRestrictedHeaders\", \"true\");\n    }\n\n    @GetMapping(\"/forecast/{cityname}\")\n    @ResponseStatus(HttpStatus.OK)\n    public String forecast(@PathVariable(\"cityname\") String cityname) {\n\n        HttpHeaders headers = new HttpHeaders();\n        headers.set(\"Host\", \"weatherbackend.server.test\");\n\n        ResponseEntity\u003cString\u003e responseEntity = restTemplate.exchange(\"http://nginx:80/weather/\" + cityname,\n                HttpMethod.GET,\n                new HttpEntity\u003cString\u003e(null, headers),\n                String.class);\n\n        return responseEntity.getBody();\n    }\n}\n\n```\n\nAs you maybe already noticed, setting the Host header in this way [is only possible](https://stackoverflow.com/a/43224279/4964553), if we set `System.setProperty(\"sun.net.http.allowRestrictedHeaders\", \"true\");`, like we do it in Spring style:\n\n```\n    /*\n     * Without the System property, we won´t be able to set the Host header, see\n     * https://stackoverflow.com/questions/43223261/setting-host-header-for-spring-resttemplate-doesnt-work/43224279\n     * and https://stackoverflow.com/a/8172736/4964553\n     */\n    @PostConstruct\n    public void setProperty() {\n        System.setProperty(\"sun.net.http.allowRestrictedHeaders\", \"true\");\n    }\n```\n\nNow our setup works perfectly fine. If you fire many requests with the help of `weatherclient`, using Springfox Swagger GUI at http://localhost:8085/swagger-ui.html#!/weatherclient45controller/forecastUsingGET and do a `docker logs NginxContainerIdHere --follow` to see the logs of Nginx and a `docker logs weatherbackendContainerIdHere` to see the logs of the `weatherbackend`, you´ll notice only __one__ call in Traefik and __one__ call of the weatherbackend:\n\n![one-call-in-traefik](one-call-in-traefik.png)\n\n![one-call-of-weatherbackend](one-call-of-weatherbackend.png)\n\nBut you´ll notice many calls inside Nginx:\n\n![many-calls-in-nginx](many-calls-in-nginx.png)\n\n\n\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjonashackt%2Ftraefik-cache-nginx-spring-boot","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjonashackt%2Ftraefik-cache-nginx-spring-boot","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjonashackt%2Ftraefik-cache-nginx-spring-boot/lists"}