1.准备 Flask 应用程序代码和 Dockerfile
1.创建一个项目目录 flask-postgres
bash
复制代码
[root@host1 ~]# mkdir -p flask-postgres && cd flask-postgres
[root@host1 flask-postgres]#
2.在该项目目录下准备一个名为 app.py 的源代码文件
bash
复制代码
# 创建数据库密码文件目录
[root@host1 flask-postgres]# mkdir db
[root@host1 flask-postgres]# vi app.py
[root@host1 flask-postgres]# cat app.py
import json
from flask import Flask
import psycopg2
import os
app = Flask(__name__)
# 读取数据库密码(支持文件/环境变量两种方式)
if 'POSTGRES_PASSWORD_FILE' in os.environ:
with open(os.environ['POSTGRES_PASSWORD_FILE'], 'r') as f:
password = f.read().strip()
else:
password = os.environ['POSTGRES_PASSWORD']
# 根路由:返回 Hello, Flask!
@app.route('/')
def hello_world():
return 'Hello, Flask!'
# 查询数据库数据的路由
@app.route('/widgets')
def get_widgets():
with psycopg2.connect(host="db", user="postgres", password=password, database="example") as conn:
with conn.cursor() as cur:
cur.execute("SELECT * FROM widgets")
row_headers = [x[0] for x in cur.description]
results = cur.fetchall()
conn.close()
json_data = [dict(zip(row_headers, result)) for result in results]
return json.dumps(json_data)
# 初始化数据库的路由(创建库和表)
@app.route('/initdb')
def db_init():
# 先创建数据库
conn = psycopg2.connect(host="db", user="postgres", password=password)
conn.set_session(autocommit=True)
with conn.cursor() as cur:
cur.execute("DROP DATABASE IF EXISTS example")
cur.execute("CREATE DATABASE example")
conn.close()
# 再创建表
with psycopg2.connect(host="db", user="postgres", password=password, database="example") as conn:
with conn.cursor() as cur:
cur.execute("DROP TABLE IF EXISTS widgets")
cur.execute("CREATE TABLE widgets (name VARCHAR(255), description VARCHAR(255))")
conn.close()
return 'init database'
if __name__ == "__main__":
app.run(host="0.0.0.0")
3.准备用于安装 Python 依赖包的 requirements.txt 文件
bash
复制代码
[root@host1 flask-postgres]# vi requirements.txt
[root@host1 flask-postgres]# cat requirements.txt
blinker==1.6.2
click==8.1.6
colorama==0.4.6
Flask==2.3.2
itsdangerous==2.1.2
Jinja2==3.1.2
MarkupSafe==2.1.3
psycopg2-binary==2.9.6
Werkzeug==2.3.6
4.准备用于构建镜像的 Dockerfile
bash
复制代码
[root@host1 flask-postgres]# vi Dockerfile
[root@host1 flask-postgres]# cat Dockerfile
# syntax=docker/dockerfile:1
ARG PYTHON_VERSION=3.11.4
FROM python:${PYTHON_VERSION}-slim as base
# 禁用 .pyc 文件生成 + 实时输出日志
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
WORKDIR /app
# 创建非特权用户
ARG UID=1001
RUN adduser \
--disabled-password \
--gecos "" \
--home "/nonexistent" \
--shell "/bin/false" \
--no-create-home \
--uid "${UID}" \
appuser
# 缓存依赖下载(加速构建)
RUN --mount=type=cache,target=/root/.cache/pip \
--mount=type=bind,source=requirements.txt,target=requirements.txt \
python -m pip install -r requirements.txt
# 切换到非特权用户
USER appuser
# 复制源代码到容器
COPY . .
# 暴露 Flask 端口
EXPOSE 5000
# 启动 Flask 应用
CMD python -m flask run --host=0.0.0.0
2.在 Compose 文件中定义服务
1.在项目目录下创建一个名为db的新目录,并在该目录中创建一个包含数据库密码的名为password.txt 的文件。为简化实验,将该文件内容(数据库密码)设置为"abel23"
bash
复制代码
[root@host1 flask-postgres]# cd db
[root@host1 db]# cd
[root@host1 ~]# cd flask-postgres/db
[root@host1 db]# vi password.txt
[root@host1 db]# cat password.txt
abc123
2.在项目目录下创建一个名为 compose.yaml 的文件来定义所需的服务和配套内容
bash
复制代码
[root@host1 flask-postgres]# cd db
[root@host1 db]# cd
[root@host1 ~]# cd flask-postgres/db
[root@host1 db]# vi password.txt
[root@host1 db]# cat password.txt
abc123
[root@host1 db]# cd flask-postgres
-bash: cd: flask-postgres: 没有那个文件或目录
[root@host1 db]# cd
[root@host1 ~]# cd flask-postgres
[root@host1 flask-postgres]# vi compose.yaml
[root@host1 flask-postgres]# cat compose.yaml
services:
# Flask 应用服务
server:
build: . # 从当前目录构建镜像
ports:
- "8000:5000" # 主机8000端口 → 容器5000端口
environment:
POSTGRES_PASSWORD_FILE: /run/secrets/db-password # 从secret读密码
depends_on:
db:
condition: service_healthy # 等数据库"健康"后再启动
secrets:
- db-password # 引用密码secret
# PostgreSQL 数据库服务
db:
image: postgres # 官方PostgreSQL镜像
restart: always # 异常时自动重启
user: postgres
secrets:
- db-password # 给数据库提供密码secret
volumes:
- db-data:/var/lib/postgresql/data # 持久化数据
environment:
POSTGRES_DB: example # 初始化的数据库名
POSTGRES_PASSWORD_FILE: /run/secrets/db-password # 从secret读密码
expose:
- "5432" # 暴露数据库内部端口
healthcheck: # 健康检查(确保数据库就绪)
test: ["CMD", "pg_isready"]
interval: 10s
timeout: 5s
retries: 5
secrets:
db-password:
file: db/password.txt # 关联本地密码文件
volumes:
db-data: # 持久化数据库数据的卷
3.查看项目目录结构,确认已经准备好如下目录和文件
bash
复制代码
[root@host1 flask-postgres]# tree .
.
├── app.py
├── compose.yaml
├── db
│ └── password.txt
├── Dockerfile
└── requirements.txt
1 directory, 5 files
3.构建镜像并运行应用程序
1.执行以下命令构建镜像并运行应用程序。-build 选项表示在启动容器之前构建镜像
bash
复制代码
[root@host1 flask-postgres]# docker compose up --build
[+] Building 273.6s (14/14) FINISHED
=> [internal] load local bake definitions 0.0s
=> => reading from stdin 507B 0.0s
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 901B 0.0s
=> resolve image config for docker-image://docker.io/docker/dockerfile:1 3.0s
=> CACHED docker-image://docker.io/docker/dockerfile:1@sha256:dabfc0969b935b2080555ace70ee69a 0.0s
=> [internal] load metadata for docker.io/library/python:3.11.4-slim 4.7s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [base 1/5] FROM docker.io/library/python:3.11.4-slim@sha256:17d62d681d9ecef20aae6c6605e9c 16.9s
=> => resolve docker.io/library/python:3.11.4-slim@sha256:17d62d681d9ecef20aae6c6605e9cf83b0b 0.0s
=> => sha256:17d62d681d9ecef20aae6c6605e9cf83b0ba3dc247013e2f43e1b5a045ad4901 1.65kB / 1.65kB 0.0s
=> => sha256:0275089b5b654bb33931fc239a447db9fdd1628bc9d1482788754785d6d9e464 1.37kB / 1.37kB 0.0s
=> => sha256:596e0d6b34dfaa7ed330941075bcd38b376b3eba8e5b63a1da38bf04fe08bdd3 6.92kB / 6.92kB 0.0s
=> => sha256:52d2b7f179e32b4cbd579ee3c4958027988f9a8274850ab0c7c24661e3adaa 29.12MB / 29.12MB 6.7s
=> => sha256:2b8a9a2240c1224b34f6aafbc3310f9a3fe65bd6893050906d02e89fc8326aa9 3.50MB / 3.50MB 4.1s
=> => sha256:051d6521462a7eb4ca0374e97701d6eec68eb51b118d3ef5d002798b498fb1 17.86MB / 17.86MB 5.9s
=> => sha256:fce84b1f897c621e9474bd4d5a49e2e22fa35e248e78e754010d34ec3d2d28cd 245B / 245B 5.3s
=> => sha256:46233543d8c2dc599bdb9d522180ca9e14cad4ac2017a5dc481660bfa4aa3ed9 3.38MB / 3.38MB 6.7s
=> => extracting sha256:52d2b7f179e32b4cbd579ee3c4958027988f9a8274850ab0c7c24661e3adaac5 4.6s
=> => extracting sha256:2b8a9a2240c1224b34f6aafbc3310f9a3fe65bd6893050906d02e89fc8326aa9 0.5s
=> => extracting sha256:051d6521462a7eb4ca0374e97701d6eec68eb51b118d3ef5d002798b498fb12e 3.5s
=> => extracting sha256:fce84b1f897c621e9474bd4d5a49e2e22fa35e248e78e754010d34ec3d2d28cd 0.0s
=> => extracting sha256:46233543d8c2dc599bdb9d522180ca9e14cad4ac2017a5dc481660bfa4aa3ed9 1.0s
=> [internal] load build context 0.0s
=> => transferring context: 4.45kB 0.0s
=> [base 2/5] WORKDIR /app 17.8s
=> [base 3/5] RUN adduser --disabled-password --gecos "" --home "/nonexistent" 0.8s
=> [base 4/5] RUN --mount=type=cache,target=/root/.cache/pip --mount=type=bind,source=r 229.4s
=> [base 5/5] COPY . . 0.0s
=> exporting to image 0.2s
=> => exporting layers 0.2s
=> => writing image sha256:c16de8f60dae3801bb185412c3242cf46e8c30413465600b81ef633b37b57d48 0.0s
=> => naming to docker.io/library/flask-postgres-server 0.0s
=> resolving provenance for metadata file 0.0s
[+] Running 5/5
✔ flask-postgres-server Built 0.0s
✔ Network flask-postgres_default Created 0.1s
✔ Volume "flask-postgres_db-data" Created 0.0s
✔ Container flask-postgres-db-1 Created 0.1s
✔ Container flask-postgres-server-1 Created 0.0s
Attaching to db-1, server-1
db-1 | The files belonging to this database system will be owned by user "postgres".
db-1 | This user must also own the server process.
db-1 |
db-1 | The database cluster will be initialized with locale "en_US.utf8".
db-1 | The default database encoding has accordingly been set to "UTF8".
db-1 | The default text search configuration will be set to "english".
db-1 |
db-1 | Data page checksums are disabled.
db-1 |
db-1 | fixing permissions on existing directory /var/lib/postgresql/data ... ok
db-1 | creating subdirectories ... ok
db-1 | selecting dynamic shared memory implementation ... posix
db-1 | selecting default "max_connections" ... 100
db-1 | selecting default "shared_buffers" ... 128MB
db-1 | selecting default time zone ... Etc/UTC
db-1 | creating configuration files ... ok
db-1 | running bootstrap script ... ok
db-1 | performing post-bootstrap initialization ... ok
db-1 | syncing data to disk ... ok
db-1 |
db-1 |
db-1 | Success. You can now start the database server using:
db-1 |
db-1 | pg_ctl -D /var/lib/postgresql/data -l logfile start
db-1 |
db-1 | initdb: warning: enabling "trust" authentication for local connections
db-1 | initdb: hint: You can change this by editing pg_hba.conf or using the option -A, or --auth-local and --auth-host, the next time you run initdb.
db-1 | waiting for server to start....2025-09-24 03:53:30.862 UTC [35] LOG: starting PostgreSQL 17.6 (Debian 17.6-1.pgdg13+1) on x86_64-pc-linux-gnu, compiled by gcc (Debian 14.2.0-19) 14.2.0, 64-bit
db-1 | 2025-09-24 03:53:30.864 UTC [35] LOG: listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"
db-1 | 2025-09-24 03:53:30.872 UTC [38] LOG: database system was shut down at 2025-09-24 03:53:30 UTC
db-1 | 2025-09-24 03:53:30.882 UTC [35] LOG: database system is ready to accept connections
db-1 | done
db-1 | server started
db-1 | CREATE DATABASE
db-1 |
db-1 |
db-1 | /usr/local/bin/docker-entrypoint.sh: ignoring /docker-entrypoint-initdb.d/*
db-1 |
db-1 | waiting for server to shut down....2025-09-24 03:53:31.533 UTC [35] LOG: received fast shutdown request
db-1 | 2025-09-24 03:53:31.534 UTC [35] LOG: aborting any active transactions
db-1 | 2025-09-24 03:53:31.540 UTC [35] LOG: background worker "logical replication launcher" (PID 41) exited with exit code 1
db-1 | 2025-09-24 03:53:31.541 UTC [36] LOG: shutting down
db-1 | 2025-09-24 03:53:31.543 UTC [36] LOG: checkpoint starting: shutdown immediate
db-1 | 2025-09-24 03:53:31.598 UTC [36] LOG: checkpoint complete: wrote 925 buffers (5.6%); 0 WAL file(s) added, 0 removed, 0 recycled; write=0.048 s, sync=0.004 s, total=0.057 s; sync files=301, longest=0.002 s, average=0.001 s; distance=4256 kB, estimate=4256 kB; lsn=0/1915960, redo lsn=0/1915960
db-1 | 2025-09-24 03:53:31.608 UTC [35] LOG: database system is shut down
db-1 | done
db-1 | server stopped
db-1 |
db-1 | PostgreSQL init process complete; ready for start up.
db-1 |
db-1 | 2025-09-24 03:53:31.695 UTC [1] LOG: starting PostgreSQL 17.6 (Debian 17.6-1.pgdg13+1) on x86_64-pc-linux-gnu, compiled by gcc (Debian 14.2.0-19) 14.2.0, 64-bit
db-1 | 2025-09-24 03:53:31.697 UTC [1] LOG: listening on IPv4 address "0.0.0.0", port 5432
db-1 | 2025-09-24 03:53:31.697 UTC [1] LOG: listening on IPv6 address "::", port 5432
db-1 | 2025-09-24 03:53:31.699 UTC [1] LOG: listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"
db-1 | 2025-09-24 03:53:31.704 UTC [51] LOG: database system was shut down at 2025-09-24 03:53:31 UTC
db-1 | 2025-09-24 03:53:31.712 UTC [1] LOG: database system is ready to accept connections
server-1 | * Debug mode: off
server-1 | WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
server-1 | * Running on all addresses (0.0.0.0)
server-1 | * Running on http://127.0.0.1:5000
server-1 | * Running on http://172.20.0.3:5000
server-1 | Press CTRL+C to quit
2.打开另一个终端窗口,执行以下命令初始化数据库
bash
复制代码
[root@host1 ~]# curl http://localhost:8000/initdb
init database
3.执行以下 curl 命令访问 Flask 应用程序测试其容器化效果,结果表明Flask 应用程序容器化成功
bash
复制代码
[root@host1 ~]# curl http://localhost:8000
Hello, Flask!
4.回原终端窗口,按Ctrl+C组合键停止应用程序
!!!部分修改过程:
bash
复制代码
[root@host1 ~]# cd flask-postgres
[root@host1 flask-postgres]# cat requirements.txt
blinker==1.6.2
click==8.1.6
colorama==0.4.6
Flask==2.3.2
itsdangerous==2.1.2
Jinja2==3.1.2
MarkupSafe==2.1.3
psycopg2-binary==2.9.6
Werkzeug==2.3.6
[root@host1 flask-postgres]# vi requirements.txt
[root@host1 flask-postgres]# cat requirements.txt
blinker==1.6.2
click==8.1.6
colorama==0.4.6
Flask==2.3.2
itsdangerous==2.1.2
Jinja2==3.1.2
MarkupSafe==2.1.3
psycopg2-binary==2.9.9
Werkzeug==2.3.6
bash
复制代码
[root@host1 ~]# mkdir flask-postgres && cd flask-postgres
mkdir: 无法创建目录 "flask-postgres": 文件已存在
[root@host1 ~]# cd flask-postgres
[root@host1 flask-postgres]# vi app.py
[root@host1 flask-postgres]# cat app.py
import json
from flask import Flask
import psycopg2
import os
app = Flask(__name__)
# 从环境变量或密钥文件中获取 PostgreSQL 密码
if 'POSTGRES_PASSWORD_FILE' in os.environ:
with open(os.environ['POSTGRES_PASSWORD_FILE'], 'r') as f:
password = f.read().strip()
else:
password = os.environ['POSTGRES_PASSWORD']
# 根路由:测试服务可用性
@app.route('/')
def hello_world():
return 'Hello, Flask!'
# /widgets 路由:查询数据库中 widgets 表的数据
@app.route('/widgets')
def get_widgets():
with psycopg2.connect(host="db", user="postgres", password=password, database="example") as conn:
with conn.cursor() as cur:
cur.execute("SELECT * FROM widgets")
row_headers = [x[0] for x in cur.description]
results = cur.fetchall()
conn.close()
json_data = [dict(zip(row_headers, result)) for result in results]
return json.dumps(json_data)
# /initdb 路由:初始化数据库(创建库和表)
@app.route('/initdb')
def db_init():
# 先连接默认库,创建 example 数据库
conn = psycopg2.connect(host="db", user="postgres", password=password)
conn.set_session(autocommit=True)
with conn.cursor() as cur:
cur.execute("DROP DATABASE IF EXISTS example")
cur.execute("CREATE DATABASE example")
conn.close()
# 连接 example 数据库,创建 widgets 表
with psycopg2.connect(host="db", user="postgres", password=password, database="example") as conn:
with conn.cursor() as cur:
cur.execute("DROP TABLE IF EXISTS widgets")
cur.execute("CREATE TABLE widgets (name VARCHAR(255), description VARCHAR(255))")
conn.close()
return 'init database'
if __name__ == "__main__":
app.run(host="0.0.0.0")
[root@host1 flask-postgres]# cat requirements.txt
blinker==1.6.2
click==8.1.6
colorama==0.4.6
Flask==2.3.2
itsdangerous==2.1.2
Jinja2==3.1.2
MarkupSafe==2.1.3
psycopg2-binary==2.9.9
Werkzeug==2.3.6
[root@host1 flask-postgres]# vi requirements.txt
[root@host1 flask-postgres]# cat requirements.txt
blinker==1.6.2
click==8.1.6
colorama==0.4.6
Flask==2.3.2
itsdangerous==2.1.2
Jinja2==3.1.2
MarkupSafe==2.1.3
psycopg2-binary==2.9.6
Werkzeug==2.3.6
[root@host1 flask-postgres]# cat Dockerfile
# syntax=docker/dockerfile:1
ARG PYTHON_VERSION=3.11.4
FROM python:${PYTHON_VERSION}-slim as base
# 禁用 .pyc 文件生成 + 实时输出日志
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
WORKDIR /app
# 创建非特权用户
ARG UID=1001
RUN adduser \
--disabled-password \
--gecos "" \
--home "/nonexistent" \
--shell "/bin/false" \
--no-create-home \
--uid "${UID}" \
appuser
# 缓存依赖下载(加速构建)
RUN --mount=type=cache,target=/root/.cache/pip \
--mount=type=bind,source=requirements.txt,target=requirements.txt \
python -m pip install -r requirements.txt
# 切换到非特权用户
USER appuser
# 复制源代码到容器
COPY . .
# 暴露 Flask 端口
EXPOSE 5000
# 启动 Flask 应用
CMD python -m flask run --host=0.0.0.0
[root@host1 flask-postgres]# vi Dockerfile
[root@host1 flask-postgres]# mkdir db
mkdir: 无法创建目录 "db": 文件已存在
[root@host1 flask-postgres]# echo "abc123" > db/password.txt
[root@host1 flask-postgres]# cat compose.yaml
services:
# Flask 应用服务
server:
build: . # 从当前目录构建镜像
ports:
- "8000:5000" # 主机8000端口 → 容器5000端口
environment:
POSTGRES_PASSWORD_FILE: /run/secrets/db-password # 从secret读密码
depends_on:
db:
condition: service_healthy # 等数据库"健康"后再启动
secrets:
- db-password # 引用密码secret
# PostgreSQL 数据库服务
db:
image: postgres # 官方PostgreSQL镜像
restart: always # 异常时自动重启
user: postgres
secrets:
- db-password # 给数据库提供密码secret
volumes:
- db-data:/var/lib/postgresql/data # 持久化数据
environment:
POSTGRES_DB: example # 初始化的数据库名
POSTGRES_PASSWORD_FILE: /run/secrets/db-password # 从secret读密码
expose:
- "5432" # 暴露数据库内部端口
healthcheck: # 健康检查(确保数据库就绪)
test: ["CMD", "pg_isready"]
interval: 10s
timeout: 5s
retries: 5
secrets:
db-password:
file: db/password.txt # 关联本地密码文件
volumes:
db-data: # 持久化数据库数据的卷
[root@host1 flask-postgres]# vi compose.yaml
[root@host1 flask-postgres]# cat compose.yaml
services:
# Flask 服务
server:
build:
context: . # 从当前目录构建镜像
ports:
- "8000:5000" # 宿主机 8000 端口映射到容器 5000 端口
environment:
- POSTGRES_PASSWORD_FILE=/run/secrets/db-password # 从密钥文件读密码
depends_on:
db:
condition: service_healthy # 等待 PostgreSQL 健康检查通过后再启动
secrets:
- db-password # 挂载数据库密码密钥
# PostgreSQL 服务
db:
image: postgres # 使用官方 PostgreSQL 镜像
restart: always # 容器退出时自动重启
user: postgres # 使用 postgres 用户运行
secrets:
- db-password # 挂载数据库密码密钥
volumes:
- db-data:/var/lib/postgresql/data # 持久化数据库数据
environment:
- POSTGRES_DB=example # 自动创建 example 数据库
- POSTGRES_PASSWORD_FILE=/run/secrets/db-password # 从密钥文件读密码
expose:
- 5432 # 暴露数据库端口(仅内部网络可见)
healthcheck: # 健康检查(确保数据库就绪)
test: ["CMD", "pg_isready"]
interval: 10s
timeout: 5s
retries: 5
# 定义密钥(从本地文件加载)
secrets:
db-password:
file: db/password.txt
# 定义数据卷(持久化数据库)
volumes:
db-data:
[root@host1 flask-postgres]# tree .
.
├── app.py
├── compose.yaml
├── db
│ └── password.txt
├── Dockerfile
├── requirements.txt
└── test_db.py
1 directory, 6 files
[root@host1 flask-postgres]# docker compose up --build
[+] Building 3.7s (12/12) FINISHED
=> [internal] load local bake definitions 0.0s
=> => reading from stdin 507B 0.0s
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 1.10kB 0.0s
=> WARN: FromAsCasing: 'as' and 'FROM' keywords' casing do not match (line 3) 0.0s
=> [internal] load metadata for docker.io/library/python:3.11.4-slim 3.3s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [base 1/5] FROM docker.io/library/python:3.11.4-slim@sha256:17d62d681d9ecef20aae6c6605e9cf 0.0s
=> [internal] load build context 0.0s
=> => transferring context: 5.34kB 0.0s
=> CACHED [base 2/5] WORKDIR /app 0.0s
=> CACHED [base 3/5] RUN adduser --disabled-password --gecos "" --home "/nonexist 0.0s
=> CACHED [base 4/5] RUN --mount=type=cache,target=/root/.cache/pip --mount=type=bind,sou 0.0s
=> [base 5/5] COPY . . 0.0s
=> exporting to image 0.0s
=> => exporting layers 0.0s
=> => writing image sha256:14a87d62855044676976c1710eb5333dcf6365e6cba287e71ee0feb45534a3f5 0.0s
=> => naming to docker.io/library/flask-postgres-server 0.0s
=> resolving provenance for metadata file 0.0s
[+] Running 3/3
✔ flask-postgres-server Built 0.0s
✔ Container flask-postgres-db-1 Running 0.0s
✔ Container flask-postgres-server-1 Recreated 10.4s
Attaching to db-1, server-1
server-1 | * Debug mode: off
server-1 | WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
server-1 | * Running on all addresses (0.0.0.0)
server-1 | * Running on http://127.0.0.1:5000
server-1 | * Running on http://172.20.0.3:5000
server-1 | Press CTRL+C to quit
bash
复制代码
[root@host1 flask-postgres]# tree .
.
├── app.py
├── compose.yaml
├── db
│ └── password.txt
├── Dockerfile
├── requirements.txt
└── test_db.py
1 directory, 6 files
[root@host1 flask-postgres]# docker compose up --build
[+] Building 3.7s (12/12) FINISHED
=> [internal] load local bake definitions 0.0s
=> => reading from stdin 507B 0.0s
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 1.10kB 0.0s
=> WARN: FromAsCasing: 'as' and 'FROM' keywords' casing do not match (line 3) 0.0s
=> [internal] load metadata for docker.io/library/python:3.11.4-slim 3.3s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [base 1/5] FROM docker.io/library/python:3.11.4-slim@sha256:17d62d681d9ecef20aae6c6605e9cf 0.0s
=> [internal] load build context 0.0s
=> => transferring context: 5.34kB 0.0s
=> CACHED [base 2/5] WORKDIR /app 0.0s
=> CACHED [base 3/5] RUN adduser --disabled-password --gecos "" --home "/nonexist 0.0s
=> CACHED [base 4/5] RUN --mount=type=cache,target=/root/.cache/pip --mount=type=bind,sou 0.0s
=> [base 5/5] COPY . . 0.0s
=> exporting to image 0.0s
=> => exporting layers 0.0s
=> => writing image sha256:14a87d62855044676976c1710eb5333dcf6365e6cba287e71ee0feb45534a3f5 0.0s
=> => naming to docker.io/library/flask-postgres-server 0.0s
=> resolving provenance for metadata file 0.0s
[+] Running 3/3
✔ flask-postgres-server Built 0.0s
✔ Container flask-postgres-db-1 Running 0.0s
✔ Container flask-postgres-server-1 Recreated 10.4s
Attaching to db-1, server-1
server-1 | * Debug mode: off
server-1 | WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
server-1 | * Running on all addresses (0.0.0.0)
server-1 | * Running on http://127.0.0.1:5000
server-1 | * Running on http://172.20.0.3:5000
server-1 | Press CTRL+C to quit
db-1 | 2025-09-24 05:02:37.739 UTC [14] LOG: checkpoint starting: immediate force wait
db-1 | 2025-09-24 05:02:37.746 UTC [14] LOG: checkpoint complete: wrote 1 buffers (0.0%); 0 WAL file(s) added, 0 removed, 0 recycled; write=0.002 s, sync=0.002 s, total=0.008 s; sync files=1, longest=0.002 s, average=0.002 s; distance=1 kB, estimate=3850 kB; lsn=0/2E66F18, redo lsn=0/2E66EC0
server-1 | [2025-09-24 05:02:37,881] ERROR in app: Exception on /initdb [GET]
server-1 | Traceback (most recent call last):
server-1 | File "/usr/local/lib/python3.11/site-packages/flask/app.py", line 2190, in wsgi_app
server-1 | response = self.full_dispatch_request()
server-1 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
server-1 | File "/usr/local/lib/python3.11/site-packages/flask/app.py", line 1486, in full_dispatch_request
server-1 | rv = self.handle_user_exception(e)
server-1 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
server-1 | File "/usr/local/lib/python3.11/site-packages/flask/app.py", line 1484, in full_dispatch_request
server-1 | rv = self.dispatch_request()
server-1 | ^^^^^^^^^^^^^^^^^^^^^^^
server-1 | File "/usr/local/lib/python3.11/site-packages/flask/app.py", line 1469, in dispatch_request
server-1 | return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)
server-1 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
server-1 | File "/app/app.py", line 44, in db_init
server-1 | with psycopg2.connect(host="db", user="postgres", password=password, database="example") as conn:
server-1 | psycopg2.InterfaceError: connection already closed
server-1 | 172.20.0.1 - - [24/Sep/2025 05:02:37] "GET /initdb HTTP/1.1" 500 -
Gracefully Stopping... press Ctrl+C again to force
Container flask-postgres-server-1 Stopping
Container flask-postgres-server-1 Stopped
Container flask-postgres-db-1 Stopping
server-1 exited with code 137
Container flask-postgres-db-1 Stopped
db-1 exited with code 137
[root@host1 flask-postgres]# curl http://localhost:8000/initdb
curl: (7) Failed to connect to localhost port 8000: 拒绝连接
[root@host1 flask-postgres]# ^C
[root@host1 flask-postgres]# docker logs flask-postgres-server-1
* Debug mode: off
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:5000
* Running on http://172.20.0.3:5000
Press CTRL+C to quit
[2025-09-24 05:02:37,881] ERROR in app: Exception on /initdb [GET]
Traceback (most recent call last):
File "/usr/local/lib/python3.11/site-packages/flask/app.py", line 2190, in wsgi_app
response = self.full_dispatch_request()
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/flask/app.py", line 1486, in full_dispatch_request
rv = self.handle_user_exception(e)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/flask/app.py", line 1484, in full_dispatch_request
rv = self.dispatch_request()
^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/flask/app.py", line 1469, in dispatch_request
return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/app/app.py", line 44, in db_init
with psycopg2.connect(host="db", user="postgres", password=password, database="example") as conn:
psycopg2.InterfaceError: connection already closed
172.20.0.1 - - [24/Sep/2025 05:02:37] "GET /initdb HTTP/1.1" 500 -
[root@host1 flask-postgres]# cat app.py
import json
from flask import Flask
import psycopg2
import os
app = Flask(__name__)
# 从环境变量或密钥文件中获取 PostgreSQL 密码
if 'POSTGRES_PASSWORD_FILE' in os.environ:
with open(os.environ['POSTGRES_PASSWORD_FILE'], 'r') as f:
password = f.read().strip()
else:
password = os.environ['POSTGRES_PASSWORD']
# 根路由:测试服务可用性
@app.route('/')
def hello_world():
return 'Hello, Flask!'
# /widgets 路由:查询数据库中 widgets 表的数据
@app.route('/widgets')
def get_widgets():
with psycopg2.connect(host="db", user="postgres", password=password, database="example") as conn:
with conn.cursor() as cur:
cur.execute("SELECT * FROM widgets")
row_headers = [x[0] for x in cur.description]
results = cur.fetchall()
conn.close()
json_data = [dict(zip(row_headers, result)) for result in results]
return json.dumps(json_data)
# /initdb 路由:初始化数据库(创建库和表)
@app.route('/initdb')
def db_init():
# 先连接默认库,创建 example 数据库
conn = psycopg2.connect(host="db", user="postgres", password=password)
conn.set_session(autocommit=True)
with conn.cursor() as cur:
cur.execute("DROP DATABASE IF EXISTS example")
cur.execute("CREATE DATABASE example")
conn.close()
# 连接 example 数据库,创建 widgets 表
with psycopg2.connect(host="db", user="postgres", password=password, database="example") as conn:
with conn.cursor() as cur:
cur.execute("DROP TABLE IF EXISTS widgets")
cur.execute("CREATE TABLE widgets (name VARCHAR(255), description VARCHAR(255))")
conn.close()
return 'init database'
if __name__ == "__main__":
app.run(host="0.0.0.0")
[root@host1 flask-postgres]# vi app.py
[root@host1 flask-postgres]# cat app.py
import json
from flask import Flask
import psycopg2
import os
import time
app = Flask(__name__)
if 'POSTGRES_PASSWORD_FILE' in os.environ:
with open(os.environ['POSTGRES_PASSWORD_FILE'], 'r') as f:
password = f.read().strip()
else:
password = os.environ['POSTGRES_PASSWORD']
@app.route('/')
def hello_world():
return 'Hello, Flask!'
@app.route('/widgets')
def get_widgets():
with psycopg2.connect(
host="db",
user="postgres",
password=password,
database="example",
connect_timeout=10,
sslmode='disable'
) as conn:
with conn.cursor() as cur:
cur.execute("SELECT * FROM widgets")
row_headers = [x[0] for x in cur.description]
results = cur.fetchall()
json_data = [dict(zip(row_headers, result)) for result in results]
return json.dumps(json_data)
@app.route('/initdb')
def db_init():
with psycopg2.connect(
host="db",
user="postgres",
password=password,
database="postgres",
connect_timeout=10,
sslmode='disable'
) as conn:
conn.set_session(autocommit=True)
with conn.cursor() as cur:
cur.execute("DROP DATABASE IF EXISTS example")
cur.execute("CREATE DATABASE example")
time.sleep(2)
with psycopg2.connect(
host="db",
user="postgres",
password=password,
database="example",
connect_timeout=10,
sslmode='disable'
) as conn:
with conn.cursor() as cur:
cur.execute("DROP TABLE IF EXISTS widgets")
cur.execute("CREATE TABLE widgets (name VARCHAR(255), description VARCHAR(255))")
return 'init database'
if __name__ == "__main__":
app.run(host="0.0.0.0")
[root@host1 flask-postgres]# cd flask-postgres
-bash: cd: flask-postgres: 没有那个文件或目录
[root@host1 flask-postgres]# docker compose down
[+] Running 3/3
✔ Container flask-postgres-server-1 Removed 0.0s
✔ Container flask-postgres-db-1 Removed 0.0s
✔ Network flask-postgres_default Removed 0.2s
[root@host1 flask-postgres]# docker compose up --build -d
[+] Building 1.1s (12/12) FINISHED
=> [internal] load local bake definitions 0.0s
=> => reading from stdin 507B 0.0s
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 1.10kB 0.0s
=> WARN: FromAsCasing: 'as' and 'FROM' keywords' casing do not match (line 3) 0.0s
=> [internal] load metadata for docker.io/library/python:3.11.4-slim 0.8s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [base 1/5] FROM docker.io/library/python:3.11.4-slim@sha256:17d62d681d9ecef20aae6c6605e9cf 0.0s
=> [internal] load build context 0.0s
=> => transferring context: 2.43kB 0.0s
=> CACHED [base 2/5] WORKDIR /app 0.0s
=> CACHED [base 3/5] RUN adduser --disabled-password --gecos "" --home "/nonexist 0.0s
=> CACHED [base 4/5] RUN --mount=type=cache,target=/root/.cache/pip --mount=type=bind,sou 0.0s
=> [base 5/5] COPY . . 0.0s
=> exporting to image 0.0s
=> => exporting layers 0.0s
=> => writing image sha256:c3772d63f2533e31ce60970b53616a26d57009b6ac939de0a21b90d7b7861c2d 0.0s
=> => naming to docker.io/library/flask-postgres-server 0.0s
=> resolving provenance for metadata file 0.0s
[+] Running 4/4
✔ flask-postgres-server Built 0.0s
✔ Network flask-postgres_default Created 0.1s
✔ Container flask-postgres-db-1 Healthy 11.0s
✔ Container flask-postgres-server-1 Started 11.4s
[root@host1 flask-postgres]# docker compose ps
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
flask-postgres-db-1 postgres "docker-entrypoint.s..." db 14 seconds ago Up 13 seconds (healthy) 5432/tcp
flask-postgres-server-1 flask-postgres-server "python -m flask run..." server 14 seconds ago Up 2 seconds 0.0.0.0:8000->5000/tcp, [::]:8000->5000/tcp
[root@host1 flask-postgres]# docker exec -u root -it flask-postgres-db-1 bash
root@7040b811a7e3:/# grep -E "host|local" /var/lib/postgresql/data/pg_hba.conf | grep -v "#"
local all all trust
host all all 127.0.0.1/32 trust
host all all ::1/128 trust
local replication all trust
host replication all 127.0.0.1/32 trust
host replication all ::1/128 trust
host all all all md5
root@7040b811a7e3:/# sed -i 's/trust/md5/g' /var/lib/postgresql/data/pg_hba.conf
root@7040b811a7e3:/# grep -E "host|local" /var/lib/postgresql/data/pg_hba.conf | grep -v "#"
local all all md5
host all all 127.0.0.1/32 md5
host all all ::1/128 md5
local replication all md5
host replication all 127.0.0.1/32 md5
host replication all ::1/128 md5
host all all all md5
root@7040b811a7e3:/# su - postgres
postgres@7040b811a7e3:~$ /usr/lib/postgresql/17/bin/pg_ctl -D /var/lib/postgresql/data restart
waiting for server to shut down....[root@host1 flask-postgres]#
[root@host1 flask-postgres]# docker compose restart server
[+] Restarting 1/1
✔ Container flask-postgres-server-1 Started 10.8s
[root@host1 flask-postgres]# docker compose ps
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
flask-postgres-db-1 postgres "docker-entrypoint.s..." db 35 minutes ago Up About a minute (healthy) 5432/tcp
flask-postgres-server-1 flask-postgres-server "python -m flask run..." server 35 minutes ago Up About a minute 0.0.0.0:8000->5000/tcp, [::]:8000->5000/tcp
[root@host1 flask-postgres]# cd
[root@host1 ~]# vi flask-postgres/app.py
[root@host1 ~]# vi flask-postgres/app.py
[root@host1 ~]# cat flask-postgres/app.py
import json
from flask import Flask
import psycopg2
import os
import time
app = Flask(__name__)
if 'POSTGRES_PASSWORD_FILE' in os.environ:
with open(os.environ['POSTGRES_PASSWORD_FILE'], 'r') as f:
password = f.read().strip()
else:
password = os.environ['POSTGRES_PASSWORD']
@app.route('/')
def hello_world():
return 'Hello, Flask!'
@app.route('/widgets')
def get_widgets():
with psycopg2.connect(
host="db",
user="postgres",
password=password,
database="example",
connect_timeout=10,
sslmode='disable'
) as conn:
with conn.cursor() as cur:
cur.execute("SELECT * FROM widgets")
row_headers = [x[0] for x in cur.description]
results = cur.fetchall()
json_data = [dict(zip(row_headers, result)) for result in results]
return json.dumps(json_data)
@app.route('/initdb')
def db_init():
# 阶段1:创建 example 数据库(关键:手动管理连接,强制 autocommit)
conn = None # 初始化连接变量
try:
# 连接默认库(postgres),直接设置 autocommit=True,不开启事务
conn = psycopg2.connect(
host="db",
user="postgres",
password=password,
database="postgres",
connect_timeout=10,
sslmode='disable'
)
conn.autocommit = True # 强制开启自动提交,彻底关闭事务
cur = conn.cursor()
cur.execute("DROP DATABASE IF EXISTS example")
cur.execute("CREATE DATABASE example")
cur.close() # 关闭游标
finally:
if conn:
conn.close() # 确保连接关闭
# 阶段2:等待新库元数据同步(不变)
time.sleep(2)
# 阶段3:创建 widgets 表(表操作可在事务内,用 with 没问题)
with psycopg2.connect(
host="db",
user="postgres",
password=password,
database="example",
connect_timeout=10,
sslmode='disable'
) as conn:
with conn.cursor() as cur:
cur.execute("DROP TABLE IF EXISTS widgets")
cur.execute("CREATE TABLE widgets (name VARCHAR(255), description VARCHAR(255))")
return 'init database'
if __name__ == "__main__":
app.run(host="0.0.0.0")
[root@host1 ~]# cd flask-postgres
[root@host1 flask-postgres]# docker compose down
[+] Running 3/3
✔ Container flask-postgres-server-1 Removed 10.3s
✔ Container flask-postgres-db-1 Removed 0.2s
✔ Network flask-postgres_default Removed 0.2s
[root@host1 flask-postgres]# docker compose up --build -d
[+] Building 2.9s (12/12) FINISHED
=> [internal] load local bake definitions 0.0s
=> => reading from stdin 507B 0.0s
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 1.10kB 0.0s
=> WARN: FromAsCasing: 'as' and 'FROM' keywords' casing do not match (line 3) 0.0s
=> [internal] load metadata for docker.io/library/python:3.11.4-slim 2.6s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [base 1/5] FROM docker.io/library/python:3.11.4-slim@sha256:17d62d681d9ecef20aae6c6605e9cf 0.0s
=> [internal] load build context 0.0s
=> => transferring context: 2.96kB 0.0s
=> CACHED [base 2/5] WORKDIR /app 0.0s
=> CACHED [base 3/5] RUN adduser --disabled-password --gecos "" --home "/nonexist 0.0s
=> CACHED [base 4/5] RUN --mount=type=cache,target=/root/.cache/pip --mount=type=bind,sou 0.0s
=> [base 5/5] COPY . . 0.0s
=> exporting to image 0.0s
=> => exporting layers 0.0s
=> => writing image sha256:f4f5195583f1bed7475ff9da158ba9018d378dd9378d06c0ba0c63f0bb286097 0.0s
=> => naming to docker.io/library/flask-postgres-server 0.0s
=> resolving provenance for metadata file 0.0s
[+] Running 4/4
✔ flask-postgres-server Built 0.0s
✔ Network flask-postgres_default Created 0.1s
✔ Container flask-postgres-db-1 Healthy 11.0s
✔ Container flask-postgres-server-1 Started 11.4s
[root@host1 flask-postgres]# docker compose ps
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
flask-postgres-db-1 postgres "docker-entrypoint.s..." db 34 seconds ago Up 33 seconds (healthy) 5432/tcp
flask-postgres-server-1 flask-postgres-server "python -m flask run..." server 33 seconds ago Up 22 seconds 0.0.0.0:8000->5000/tcp, [::]:8000->5000/tcp
[root@host1 flask-postgres]#
bash
复制代码
[root@host1 ~]# curl http://localhost:8000/initdb
init database