{"id":24732735,"url":"https://github.com/gonzalo123/flask-traefik","last_synced_at":"2026-04-17T00:31:07.537Z","repository":{"id":170036993,"uuid":"646146058","full_name":"gonzalo123/flask-traefik","owner":"gonzalo123","description":"Deploying Python Applications in a Docker Swarm Cluster with Traefik Reverse Proxy and HTTPS","archived":false,"fork":false,"pushed_at":"2025-08-21T09:46:26.000Z","size":30,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-08-21T11:52:01.868Z","etag":null,"topics":["docker","docker-swarm","flask","nginx","python","traefik"],"latest_commit_sha":null,"homepage":"","language":"Dockerfile","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/gonzalo123.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"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,"zenodo":null}},"created_at":"2023-05-27T12:39:41.000Z","updated_at":"2025-08-21T09:46:30.000Z","dependencies_parsed_at":null,"dependency_job_id":"ced3a85b-774f-4d3b-9397-853fdf3e8be7","html_url":"https://github.com/gonzalo123/flask-traefik","commit_stats":null,"previous_names":["gonzalo123/flask-traefik"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/gonzalo123/flask-traefik","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gonzalo123%2Fflask-traefik","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gonzalo123%2Fflask-traefik/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gonzalo123%2Fflask-traefik/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gonzalo123%2Fflask-traefik/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/gonzalo123","download_url":"https://codeload.github.com/gonzalo123/flask-traefik/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gonzalo123%2Fflask-traefik/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31909835,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-16T18:22:33.417Z","status":"ssl_error","status_checked_at":"2026-04-16T18:21:47.142Z","response_time":69,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":["docker","docker-swarm","flask","nginx","python","traefik"],"created_at":"2025-01-27T17:53:50.255Z","updated_at":"2026-04-17T00:31:07.531Z","avatar_url":"https://github.com/gonzalo123.png","language":"Dockerfile","funding_links":[],"categories":[],"sub_categories":[],"readme":"## Deploying Python Applications in a Docker Swarm Cluster with Traefik Reverse Proxy and HTTPS\n\nIn this article, we will explore the process of deploying Python applications in a Docker Swarm cluster, utilizing a Traefik reverse proxy and ensuring secure communication through HTTPS. By following this approach, we can enhance the scalability, availability, and security of our Python applications.\n\nHTTP is not a secure protocol. When deploying a web service using HTTP, it is important to be aware that anyone can intercept the traffic between the browser and the server. An attacker simply needs to use a sniffer tool like Wireshark, for example, to view the traffic in plain text, including passwords and sensitive data. The solution to this issue is to use the HTTPS protocol. HTTPS provides two important benefits: first, it ensures that the server is who it claims to be through certificates, and second, it encrypts the traffic between the client and server. When exposing anything to the internet, the use of HTTPS is considered mandatory. However, in some cases, such as internal APIs within a local network, HTTPS may not be utilized. In this article, we will focus on enabling HTTPS for services in a Docker Swarm cluster. Let's get started.\n\nTo achieve this, we will utilize Traefik as a reverse proxy, serving as the sole entry point for our services deployed within the Swarm cluster. Our deployed stacks will not directly expose any ports outside the cluster; instead, they will be mapped to Traefik. Traefik will then handle the task of exposing these services on specific paths. To establish this setup, both our stacks and Traefik will utilize the same external network. Therefore, the first step is to define this network within our cluster.\n\n```shell\ndocker network create --driver overlay external-net\n```\n\nThat's our Traefik service configuration\n\n```yaml\nversion: \"3.9\"\n\nservices:\n  traefik:\n    image: traefik:v2.10\n    command:\n      - \"--log.level=INFO\"\n      - \"--api.insecure=false\"\n      - \"--api=true\"\n      - \"--api.dashboard=true\"\n      - \"--providers.docker=true\"\n      - \"--providers.docker.exposedByDefault=false\"\n      - \"--entrypoints.web.address=:80\"\n      - \"--entrypoints.websecure.address=:443\"\n      - \"--entrypoints.web.http.redirections.entryPoint.to=websecure\"\n      - \"--entrypoints.web.http.redirections.entryPoint.scheme=https\"\n    environment:\n      - TZ=Europe/Madrid\n    ports:\n      - \"80:80\"\n      - \"443:443\"\n    volumes:\n      - \"/var/run/docker.sock:/var/run/docker.sock:ro\"\n\n    networks:\n      - external-net\n\nnetworks:\n  external-net:\n    external: true\n```\n\nNow, let's define our service. In this example, we will have three replicas of a Flask API backend behind a Nginx proxy, which is a common Python scenario.\n\n```python\nfrom flask import Flask\nimport os\n\napp = Flask(__name__)\n\n\n@app.get(\"/service1\")\ndef health():\n    return dict(\n        status=True,\n        slot=os.getenv('SLOT')\n    )\n```\n\nAnd that's the configuration of the service:\n\n```yaml\nservices:\n  traefik:\n    image: traefik:v3.1\n    command:\n      - \"--log.level=INFO\"\n      - \"--providers.docker=true\"\n      - \"--providers.docker.exposedByDefault=false\"\n      # Entrypoints\n      - \"--entrypoints.web.address=:80\"\n      - \"--entrypoints.websecure.address=:443\"\n      - \"--entrypoints.web.http.redirections.entrypoint.to=websecure\"\n      - \"--entrypoints.web.http.redirections.entrypoint.scheme=https\"\n    environment:\n      - TZ=Europe/Madrid\n    ports:\n      - \"80:80\"\n      - \"443:443\"\n    volumes:\n      - \"/var/run/docker.sock:/var/run/docker.sock:ro\"\n    networks:\n      - external-net\n\nnetworks:\n  external-net:\n    external: true\n```\n\nIt uses two images, one for the Flask backend. As we can see in the Dockerfile, we are utilizing a Python 3.11 base image. In the Dockerfile, we set up a non-root user, configure the container, and install dependencies using Poetry.\n\n```Dockerfile\nFROM python:3.11 AS base\n\nENV PYTHONDONTWRITEBYTECODE 1\nENV PYTHONUNBUFFERED 1\nENV APP_HOME=/src\nENV APP_USER=appuser\n\nRUN groupadd -r $APP_USER \u0026\u0026 \\\n    useradd -r -g $APP_USER -d $APP_HOME -s /sbin/nologin -c \"Docker image user\" $APP_USER\n\nENV TZ 'Europe/Madrid'\nRUN echo $TZ \u003e /etc/timezone \u0026\u0026 \\\n    apt-get update \u0026\u0026 apt-get install --no-install-recommends -y tzdata \u0026\u0026 \\\n    rm /etc/localtime \u0026\u0026 \\\n    ln -snf /usr/share/zoneinfo/$TZ /etc/localtime \u0026\u0026 \\\n    dpkg-reconfigure -f noninteractive tzdata \u0026\u0026 \\\n    apt-get clean\n\nRUN pip install --upgrade pip poetry\n\nFROM base\n\nWORKDIR $APP_HOME\nCOPY --chown=$APP_USER:$APP_USER pyproject.toml poetry.lock ./\n\nRUN poetry config virtualenvs.create false \u0026\u0026 \\\n    poetry install --no-root --no-interaction --no-ansi --no-dev\n\nCOPY --chown=$APP_USER:$APP_USER src $APP_HOME\nRUN find \"$APP_HOME\" -name '__pycache__' -type d -exec rm -r {} +\n\nRUN chown -R $APP_USER:$APP_USER $APP_HOME\n\nUSER $APP_USER\n```\n\nWe also have a Nginx proxy that serves the replicas of the backend.\n\n```nginx configuration\nupstream loadbalancer {\n    server backend:5000;\n}\n\nserver {\n    server_tokens off;\n    client_max_body_size 20M;\n    proxy_busy_buffers_size   512k;\n    proxy_buffers   4 512k;\n    proxy_buffer_size   256k;\n    proxy_set_header Host $host;\n    add_header X-Frame-Options SAMEORIGIN;\n    real_ip_header X-Forwarded-For;\n    real_ip_recursive on;\n    set_real_ip_from 0.0.0.0/0;\n    proxy_read_timeout 300;\n    proxy_connect_timeout 300;\n    proxy_send_timeout 300;\n\n    location /service1 {\n        proxy_pass http://loadbalancer;\n    }\n\n    location /service1/health {\n        default_type text/html;\n        access_log off;\n        return 200 'Ok!';\n    }\n}\n```\n\n```Dockerfile\nFROM nginx:1.23.4-alpine-slim\n\nRUN rm /etc/nginx/conf.d/default.conf\nCOPY nginx.conf /etc/nginx/conf.d\n```\n\nWith those two containers we can set up the service\n\n```yaml\nservices:\n  nginx:\n    image: flaskdemo_nginx:latest\n    labels:\n      - \"traefik.enable=true\"\n      - \"traefik.http.routers.app.tls=true\"\n      - \"traefik.http.routers.app.rule=Host(`localhost`) \u0026\u0026 PathPrefix(`/service1`)\"\n      - \"traefik.http.services.app.loadbalancer.server.port=80\"\n    depends_on:\n      - backend\n    networks:\n      - external-net\n      - default-net\n\n  backend:\n    image: flaskdemo:latest\n    command: gunicorn -w 1 app:app -b 0.0.0.0:5000 --timeout 180\n    environment:\n      SLOT: \"{{.Task.Slot}}\"\n    deploy:\n      replicas: 3\n    networks:\n      - default-net\n\nnetworks:\n  default-net:\n  external-net:\n    external: true\n\n```\n\nAs we can observe, the magic of Traefik lies within the labels assigned to the exposed service, which in our case is Nginx. These labels define the path that Traefik will utilize to serve the service, specified as \"/service1\" in our example. Additionally, we instruct Traefik to use HTTPS for this service. It is crucial to ensure that our exposed Nginx service is placed within the same external network as Traefik, as demonstrated by the \"external-net\" in our example.\n\nOn the other hand, the backend service, represented by our Flask application, does not necessarily need to reside in this network. In fact, it is preferable to segregate our service networks, utilizing a private non-external network, such as the \"default-net,\" to establish communication solely between Nginx and the backend.\n\nNote: With this configuration, we are utilizing the default HTTPS certificate provided by Traefik. It should be noted that this certificate does not guarantee server authority, which may result in browser warnings. However, it does ensure that the traffic is encrypted. Alternatively, there are other options available such as using a self-signed certificate, purchasing a certificate from a certificate authority, or obtaining a free valid certificate from Let's Encrypt. However, these alternatives are beyond the scope of this post.\n\nNow ce can build our containers\n\n```shell\ndocker build -t flaskdemo .\ndocker build -t flaskdemo_nginx .docker/nginx\n\ndocker build -t flaskdemo_traefik .docker/traefik\n```\n\nand deploy to our Swarm cluster (in my example at localhost)\n\n```shell\ndocker stack deploy -c traefik-stack.yml traefik\ndocker stack deploy -c service-stack.yml service1\n```\n\nGenerate self-signed certificates\n\n```bash\nopenssl req -x509 -nodes -newkey rsa:2048 \\\n  -keyout server.key \\\n  -out server.crt \\\n  -days 3650 \\\n  -subj \"/CN=tu-localhost\"\n```\n\nAnd that's it! Our private API is now up and running, utilizing HTTPS for secure communication.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgonzalo123%2Fflask-traefik","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgonzalo123%2Fflask-traefik","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgonzalo123%2Fflask-traefik/lists"}