---
# System prepended metadata

title: Traefik
tags: [docker]

---

# 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)