# Traefik [toc] full code -- https://github.com/XuanLin123/Traefik-Flask-PostgreSQL.git ## Running Without Compose (test) [traefik_dockerhub](https://hub.docker.com/_/traefik) **test part just follow the step** 1. test dir ```bash= $ pwd /home/user/Desktop/traefik $ ls traefik.yml ``` 2. edit traefik.yml ```yaml= # enable docker and web UI providers: docker: defaultRule: "Host(`{{ trimPrefix `/` .Name }}.docker.localhost`)" api: insecure: true ``` 3. run ```bash= $ docker run -d -p 8080:8080 -p 80:80 \ -v $PWD/traefik.yml:/etc/traefik/traefik.yml \ -v /var/run/docker.sock:/var/run/docker.sock \ traefik:v2.5 ``` 4. increase test ```bash= $ docker run -d --name test traefik/whoami ``` 5. curl ```bash= $ curl test.docker.localhost ``` ![image](https://hackmd.io/_uploads/S1gO_XoEC.png) 6. connect http://localhost:8080/ ![image](https://hackmd.io/_uploads/ryAadQo4C.png) --------------- ## Increase 2 web server **apache & nginx** **image httpd:alpine & nginx:alpine** **file structure** ```bash= user@vm:~/Desktop/traefik$ tree . ├── apache │ ├── Dockerfile │ └── index.html ├── docker-compose.yml ├── flask-app │ ├── app.py │ ├── Dockerfile │ └── requirements.txt ├── nginx │ ├── Dockerfile │ └── index.html └── traefik └── traefik.yml 4 directories, 8 files ``` * apache file **Dockerfile** ```dockerfile= FROM httpd:alpine COPY index.html /usr/local/apache2/htdocs/index.html ``` **simple html** ```html= <!DOCTYPE html> <html> <head> <title>Apache</title> </head> <body> <h1>Hello from Apache!</h1> </body> </html> ``` * nginx file **Dockerfile** ```dockerfile= FROM nginx:alpine COPY index.html /usr/share/nginx/html/index.html ``` **simple html** ```html= <!DOCTYPE html> <html> <head> <title>Nginx</title> </head> <body> <h1>Hello from Nginx!</h1> </body> </html> ``` * docker-compose ```yaml= version: '3.8' services: traefik: image: traefik:v2.5 command: - "--api.insecure=true" - "--providers.docker=true" - "--entrypoints.web.address=:80" ports: - "80:80" # for lieten http - "8080:8080" # for traefik volumes: - /var/run/docker.sock:/var/run/docker.sock - ./traefik/traefik.yml:/traefik.yml nginx: build: ./nginx labels: - "traefik.enable=true" - "traefik.http.routers.nginx.rule=Host(`nginx.localhost`)" # address change - "traefik.http.services.nginx.loadbalancer.server.port=80" apache: build: ./apache labels: - "traefik.enable=true" - "traefik.http.routers.apache.rule=Host(`apache.localhost`)" # address change - "traefik.http.services.apache.loadbalancer.server.port=80" ``` ![image](https://hackmd.io/_uploads/BywokxXBR.png) ## Flask + PostgreSQL **image postgres:13 & python:3.9-slim** **file structure** ```bash= user@vm:~/Desktop/traefik$ tree . ├── apache │ ├── Dockerfile │ └── index.html ├── db │ ├── Dockerfile │ ├── init.sql │ └── testdata │ └── test-01.sql ├── docker-compose.yml ├── flask-app │ ├── app.py │ ├── Dockerfile │ └── requirements.txt ├── nginx │ ├── Dockerfile │ └── index.html └── traefik └── traefik.yml 6 directories, 12 files ``` **image python:3.9-slim** * Flask page test **app.py** ```python= from flask import Flask app = Flask(__name__) @app.route('/') def index(): return '<h1>Hello from Flask!</h1>' if __name__ == '__main__': app.run(host='0.0.0.0') ``` **Dockerfile** ```dockerfile= FROM python:3.9-slim WORKDIR /app COPY requirements.txt requirements.txt RUN pip install -r requirements.txt COPY ./app.py . CMD [ "python", "app.py" ] ``` **edit docker-compose.yml** ```yaml= flask-app: build: ./flask-app labels: - "traefik.enable=true" - "traefik.http.routers.flask.rule=Host(`flask.localhost`)" - "traefik.http.services.flask.loadbalancer.server.port=5000" ``` **success** ![image](https://hackmd.io/_uploads/rkwQbxQrR.png) * PostgreSQL * Initialize Database `./db/init.sql` ```sql= CREATE DATABASE addrbook; \connect addrbook; CREATE TABLE users ( id serial PRIMARY KEY, username VARCHAR(50) NOT NULL, email VARCHAR(100) NOT NULL ); ``` * testdata `$ mkdir testdata && cd testdata` `test-01.sql` ```sql= \connect addrbook; INSERT INTO users (username, email) VALUES ('Alice', 'alice@example.com'), ('Jobs', 'jobs@example.com'), ('Tom', 'tom@example.com'); ``` * Dockerfile ```dockerfile= FROM postgres:13 COPY *.sql /docker-entrypoint-initdb.d/ COPY ./testdata/*.sql /docker-entrypoint-initdb.d/ ENV POSTGRES_USER=myuser ENV POSTGRES_PASSWORD=mypassword ENV POSTGRES_DB=addrbook EXPOSE 5432 ``` * Flask 1. app.py ```python= from flask import Flask, request, jsonify import psycopg2 app = Flask(__name__) def get_db_connection(): conn = psycopg2.connect( host='db', database='addrbook', user='myuser', password='mypassword' ) return conn @app.route('/') def index(): return '<h1>Hello from Flask!</h1>' @app.route('/users', methods=['GET', 'POST']) def users(): conn = get_db_connection() cur = conn.cursor() cur.execute('SELECT * FROM users') rows = cur.fetchall() return jsonify(rows) if __name__ == '__main__': app.run(host='0.0.0.0') ``` 2. requirements.txt ``` flask psycopg2-binary ``` 3. Dockerfile ```dockerfile= FROM python:3.9-slim WORKDIR /app COPY requirements.txt requirements.txt RUN pip install -r requirements.txt COPY ./app.py . CMD [ "python", "app.py" ] ``` * docker-compose.yml ```yaml= version: '3.8' services: traefik: image: traefik:v2.5 command: - "--api.insecure=true" - "--providers.docker=true" - "--entrypoints.web.address=:80" ports: - "80:80" - "8080:8080" volumes: - /var/run/docker.sock:/var/run/docker.sock - ./traefik/traefik.yml:/traefik.yml flask-app: build: ./flask-app labels: - "traefik.enable=true" - "traefik.http.routers.flask.rule=Host(`flask.localhost`)" - "traefik.http.services.flask.loadbalancer.server.port=5000" depends_on: - db db: build: db environment: POSTGRES_PASSWORD: mypassword volumes: - db_data:/var/lib/postgresql/data # persistence volumes: db_data: ``` * enter container ```bash= $ docker exec -it traefik-db-1 psql -U myuser -d addrbook ``` ```sql= addrbook=# SELECT * FROM users; id | username | email ----+----------+------------------- 1 | Alice | alice@example.com 2 | Jobs | jobs@example.com 3 | Tom | tom@example.com (3 rows) ``` ## Traefik + Flask + PostgreSQL Combining Traefik, Flask, and PostgreSQL, data can be inputted through a simple web page and displayed on the webpage. **file structure** ```bash= user@vm:~/Desktop/traefik$ tree . ├── apache # retain the apache directory, and the final result can be viewed. │ ├── Dockerfile │ └── index.html ├── db # PostgreSQL initialization & test data │ ├── Dockerfile │ ├── init.sql │ └── testdata │ └── test-01.sql ├── docker-compose.yml ├── flask-app # Web Page & data transmission │ ├── app.py │ ├── Dockerfile │ ├── requirements.txt │ └── templates │ ├── contact_complete.html │ ├── contact.html │ └── users.html ├── nginx # retain the nginx directory, and the final result can be viewed. │ ├── Dockerfile │ └── index.html └── traefik └── traefik.yml 7 directories, 15 files ``` ### PostgreSQL * Initialize Database **Same as before** `./db/init.sql` ```sql= CREATE DATABASE addrbook; \connect addrbook; CREATE TABLE users ( id serial PRIMARY KEY, username VARCHAR(50) NOT NULL, email VARCHAR(100) NOT NULL ); ``` * testdata **Same as before** `$ mkdir testdata && cd testdata` `test-01.sql` ```sql= \connect addrbook; INSERT INTO users (username, email) VALUES ('Alice', 'alice@example.com'), ('Jobs', 'jobs@example.com'), ('Tom', 'tom@example.com'); ``` * Dockerfile **Same as before** ```dockerfile= FROM postgres:13 COPY *.sql /docker-entrypoint-initdb.d/ COPY ./testdata/*.sql /docker-entrypoint-initdb.d/ ENV POSTGRES_USER=myuser ENV POSTGRES_PASSWORD=mypassword ENV POSTGRES_DB=addrbook EXPOSE 5432 ``` ### Flask Web Page ```bash= $ mkdir templates && cd templates # directory name must be the same ``` * requirements.txt **Same as before** ``` flask psycopg2-binary ``` * contact.html `./flask-app/templates/contact.html` ```html= <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>Contact Form</title> </head> <body> <h2>Contact Form</h2> <form action="{{ url_for('contact_complete') }}" # Specify the URL to which the data will be sent when the form is submitted. method="post" # Specify the HTTP method for form submission. novalidate="novalidate" # Disable HTML5 form validation. > <table> <tr> <td>Username</td> # username input box <td> <input type="text" name="username" value="{{ username }}" placeholder="Username" /> </td> </tr> <tr> <td>Mail Address</td> # mail input box <td> <input type="text" name="email" value="{{ email }}" placeholder="Email Address" /> </td> </tr> </table> <input type="submit" value="Send" /> </form> <br> <button onclick="window.location.href='/users'">Users List</button> </body> </html> ``` * contact_complete.html `./flask-app/templates/contact/complete.html` ```html= <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>Success</title> </head> <body> <h2>Success</h2> </body> <br> <button onclick="window.location.href='/contact'">Input Contact</button> <br> <button onclick="window.location.href='/users'">Users List</button> </html> ``` * users.html `./flask-app/templates/users.html` ```html= <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Users List</title> <style> table { width: 100%; border-collapse: collapse; } th, td { padding: 8px 12px; border: 1px solid #ddd; text-align: left; } th { background-color: #f4f4f4; } </style> </head> <body> <h1>Users List</h1> <table> <thead> <tr> <th>ID</th> <th>Username</th> <th>Email</th> </tr> </thead> <tbody> {% for user in users %} <tr> <td>{{ user[0] }}</td> <td>{{ user[1] }}</td> <td>{{ user[2] }}</td> <td>{{ user[3] }}</td> </tr> {% endfor %} </tbody> </table> <br> <button onclick="window.location.href='/contact'">Input Contact</button> </body> </html> ``` * edit app.py ```python= @app.route('/users', methods=['GET', 'POST']) def users(): conn = get_db_connection() cur = conn.cursor() cur.execute('SELECT * FROM users') rows = cur.fetchall() return render_template('users.html', users=rows) @app.route('/contact') # The data will be passed down and processed in "/contact/complete". def contact(): return render_template("contact.html") @app.route('/contact/complete', methods=['GET', 'POST']) def contact_complete(): if request.method =='POST': username = request.form["username"] email = request.form["email"] conn = get_db_connection() cur = conn.cursor() cur.execute('INSERT INTO users (username, email) VALUES (%s, %s)', (username, email)) conn.commit() return redirect(url_for("contact_complete")) return render_template("contact_complete.html") ``` * update Dockerfile ```dockerfile= FROM python:3.9-slim WORKDIR /app COPY requirements.txt requirements.txt RUN pip install -r requirements.txt COPY ./app.py . COPY ./templates ./templates # name must be the same CMD [ "python", "app.py" ] ``` * Final docker-compose.yml **"traefik.http.routers.flask.rule=Host(`192.168.68.154`)" Change to the desired location.** ```yaml= version: '3.8' services: traefik: image: traefik:v2.5 command: - "--api.insecure=true" - "--providers.docker=true" - "--entrypoints.web.address=:80" ports: - "80:80" - "8080:8080" volumes: - /var/run/docker.sock:/var/run/docker.sock - ./traefik/traefik.yml:/traefik.yml nginx: build: ./nginx labels: - "traefik.enable=true" - "traefik.http.routers.nginx.rule=Host(`nginx.localhost`)" - "traefik.http.services.nginx.loadbalancer.server.port=80" apache: build: ./apache labels: - "traefik.enable=true" - "traefik.http.routers.apache.rule=Host(`apache.localhost`)" - "traefik.http.services.apache.loadbalancer.server.port=80" flask-app-1: build: ./flask-app labels: - "traefik.enable=true" - "traefik.http.routers.flask.rule=Host(`192.168.68.154`)" - "traefik.http.services.flask.loadbalancer.server.port=5000" depends_on: - db flask-app-2: build: ./flask-app labels: - "traefik.enable=true" - "traefik.http.routers.flask.rule=Host(`192.168.68.154`)" - "traefik.http.services.flask.loadbalancer.server.port=5000" depends_on: - db flask-app-3: build: ./flask-app labels: - "traefik.enable=true" - "traefik.http.routers.flask.rule=Host(`192.168.68.154`)" - "traefik.http.services.flask.loadbalancer.server.port=5000" depends_on: - db flask-app-4: build: ./flask-app labels: - "traefik.enable=true" - "traefik.http.routers.flask.rule=Host(`192.168.68.154`)" - "traefik.http.services.flask.loadbalancer.server.port=5000" depends_on: - db flask-app-5: build: ./flask-app labels: - "traefik.enable=true" - "traefik.http.routers.flask.rule=Host(`192.168.68.154`)" - "traefik.http.services.flask.loadbalancer.server.port=5000" depends_on: - db db: build: ./db environment: POSTGRES_PASSWORD: mypassword volumes: - db_data:/var/lib/postgresql/data volumes: db_data: ``` * Final result **From Windows Browser Search** ![image](https://hackmd.io/_uploads/r1yUWPVBA.png) **http://192.168.68.154/contact** ![image](https://hackmd.io/_uploads/rk5vbDNrC.png) **http://192.168.68.154/contact/complete** ![image](https://hackmd.io/_uploads/SJIj-vErC.png) **http://192.168.68.154/users** ![image](https://hackmd.io/_uploads/HygpWw4rC.png) **Traefik Page Routers** You can see the routing of the services started in the compose file here. ( nginx, apache, flask) ![image](https://hackmd.io/_uploads/HJeOzvVHC.png) **Traefik Page Services** Here you can see all the services started in the compose file. ![image](https://hackmd.io/_uploads/SkawQPEB0.png) ## Reference [Docker | 建立 PostgreSQL 的 container 時,同時完成資料庫的初始化](https://eandev.com/post/container/docker-postgresql-initialization-scripts/) [Traefik Proxy Documentation](https://doc.traefik.io/traefik/) [Dockerizing Flask with Postgres, Gunicorn, and Traefik](https://testdriven.io/blog/flask-docker-traefik/) [【Traefik 教學】用10分鐘學會Traefik v2的安裝 + 自動續約Lets Encrypt](https://www.hellosanta.com.tw/knowledge/category-38/post-416)