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

6. connect http://localhost:8080/

---------------
## 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"
```

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

* 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**

**http://192.168.68.154/contact**

**http://192.168.68.154/contact/complete**

**http://192.168.68.154/users**

**Traefik Page Routers**
You can see the routing of the services started in the compose file here. ( nginx, apache, flask)

**Traefik Page Services**
Here you can see all the services started in the compose file.

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