Ubuntu 18.04 + Docker + Flask 生产部署实战指南
2026/6/22 2:28:17 网站建设 项目流程

1. 项目概述:为什么在 Ubuntu 18.04 上用 Docker 跑 Flask 不是“炫技”,而是生产级落地的必然选择

你手头有个 Flask 小项目,本地跑得飞快,flask run一敲,浏览器里http://127.0.0.1:5000立马出来个欢迎页。但当你兴冲冲把它扔给测试同事,对方回一句“我这报错:ModuleNotFoundError: No module named 'flask'”,或者部署到公司那台老服务器上,发现 Python 版本是 3.6,而你的代码强依赖flask>=2.3.0——这时候你就明白了:Flask 本身轻量,但它的运行环境从来都不轻量。Ubuntu 18.04 这个发行版,恰恰卡在一个非常典型的“生产现实”节点上:它自带的 Python 是 3.6,系统包管理器(apt)里的 Flask 版本停留在 1.x,而你写的代码可能已经用上了async def路由、flask-sqlalchemy 3.x的新语法。直接pip install -r requirements.txt?行,但会污染系统 Python 环境,下次运维要装个别的工具,可能就因为 Flask 版本冲突导致整个服务崩掉。这就是为什么标题里那个看似平平无奇的“Создание и развертывание приложения Flask с использованием Docker в Ubuntu 18.04”(在 Ubuntu 18.04 上创建并部署 Flask 应用)不是一句俄语翻译练习,而是一套完整的、面向真实世界的交付方案。Docker 在这里扮演的角色,不是锦上添花的容器化玩具,而是环境隔离的保险丝、依赖版本的刻度尺、部署流程的标准化模具。它把“我的电脑上能跑”这个玄学问题,转化成“这个镜像在任何装了 Docker 的 Ubuntu 18.04 机器上都能跑”的确定性答案。你不需要说服运维去升级系统 Python,也不需要为每个新项目单独配一个 virtualenv 并手动记录所有 pip 包版本——Dockerfile 里一行FROM python:3.9-slim就锁死了 Python 大版本,COPY requirements.txt . && pip install -r requirements.txt就固化了所有依赖。这种确定性,对个人开发者是省心,对小团队是协作基石,对上线前的 QA 流程则是质量保障。我做过一个图书管理系统的前后端分离项目,后端用 Flask 提供 REST API,前端是 Vue;当时测试环境和预发环境都是 Ubuntu 18.04,光是解决flask-corsflask-sqlalchemy在不同 Python 小版本下的兼容性问题,就花了整整两天。后来我把整个后端打包进 Docker 镜像,从开发机 build 出来,直接docker load到测试服务器,docker run启动,API 接口毫秒级响应,连跨域配置都原封不动——那一刻我才真正理解,Docker 不是让部署变“酷”,而是让部署变“可预期”。所以,这篇文章不讲 Docker 是什么、Flask 是什么这些基础概念,我们默认你已经写过几个路由、知道app.run()怎么用。我们要做的是:手把手,从零开始,在一台干净的 Ubuntu 18.04 虚拟机上,把一个真实的、带数据库连接、带配置管理的 Flask 应用,变成一个可以一键启动、随时迁移、永不依赖宿主机环境的 Docker 容器。你会看到每一个命令背后的意图,每一个配置项的取舍理由,以及那些只有踩过坑的人才知道的“千万别这样干”的实操细节。

2. 整体设计与思路拆解:为什么选 Ubuntu 18.04 + Docker + Flask 这个组合,而不是其他方案

2.1 为什么是 Ubuntu 18.04,而不是更新的 20.04 或 22.04?

这个问题很实际。现在网上绝大多数 Docker 教程,开篇就是sudo apt update && sudo apt install docker.io,然后一路顺风。但如果你真去 Ubuntu 22.04 上试,会发现docker.io包的版本是 20.10,而 Docker 官方早已停止对这个版本的支持;更麻烦的是,很多企业内网的物理服务器或云主机,操作系统镜像库是冻结的,管理员只允许你用 18.04 LTS(长期支持版),因为它的安全更新会持续到 2028 年。这意味着,Ubuntu 18.04 不是一个过时的选择,而是一个被大量生产环境强制锁定的现实基线。它的内核是 4.15,glibc 版本是 2.27,这些底层组件决定了你能跑什么版本的 Docker 引擎。Docker 官方明确要求,Docker Engine 20.10+ 需要内核 3.10+,这在 18.04 上完全满足;但如果你试图在 18.04 上安装 Docker Desktop(那个带 GUI 的 Windows/macOS 工具),就会失败——因为 Desktop 依赖 WSL2 或 Hyper-V,而 Ubuntu 18.04 是 Linux 发行版,它只跑 Docker Engine。所以,我们的设计起点非常清晰:目标平台是 Ubuntu 18.04 Server(无图形界面),我们只安装和使用 Docker Engine,这是最精简、最稳定、也最符合生产部署逻辑的路径。放弃 Docker Desktop,不是妥协,而是回归本质:服务器不需要桌面,只需要一个可靠的、能拉镜像、能跑容器的守护进程。

2.2 为什么 Flask 应用必须用 Docker,而不是直接用 Gunicorn + systemd?

这是个经典的“够用就好”和“面向未来”的分水岭。你可以完全不用 Docker:写个gunicorn --bind 0.0.0.0:8000 --workers 4 app:app命令,再配个 systemd service 文件,systemctl enable myflask.service,服务就起来了。这绝对可行,而且很多小项目至今还在这么干。但问题在于“扩展性”和“一致性”。假设你的 Flask 应用明天要接入 Redis 做缓存,后天要连 PostgreSQL 替代 SQLite,大后天要加一个 Celery 异步任务队列——你怎么办?一个个在宿主机上apt install redis-server postgresql celery?然后手动配密码、开防火墙端口、写各自的 systemd 服务?这很快就会变成一场运维噩梦。而 Docker 的设计哲学是“一个容器,一个进程,职责单一”。你的 Flask 应用容器只管跑 Python 代码,Redis 容器只管提供内存数据库,PostgreSQL 容器只管持久化数据。它们之间通过 Docker 内部网络通信,IP 地址和端口对彼此是透明的。你只需要一个docker-compose.yml文件,把这三四个服务定义好,docker-compose up -d一条命令,整套环境就拉起来了。更重要的是,这个docker-compose.yml文件,就是你的环境说明书。开发、测试、预发、生产,四套环境,只要 Docker 版本一致,docker-compose up的结果就必然一致。没有“开发机上好好的,测试机上就报错”的诡异问题。所以,我们选择 Docker,不是因为它比 Gunicorn 高级,而是因为它把“环境”这个模糊的概念,变成了可版本控制、可重复构建、可精确复制的代码资产。docker-compose.yml就是你的基础设施即代码(IaC)的第一行。

2.3 为什么基础镜像选python:3.9-slim,而不是python:3.9ubuntu:18.04

这是 Dockerfile 里最核心的一次选型,直接决定了镜像大小、安全性和构建速度。python:3.9这个官方镜像是基于 Debian 的,它包含了完整的apt包管理器、gcc编译器、各种开发头文件(python3-dev,libpq-dev等)。好处是,如果你的requirements.txt里有psycopg2-binary这种需要编译的包,它能直接pip install成功。坏处是,这个镜像体积巨大,通常超过 900MB。而python:3.9-slim是它的精简版,去掉了所有非运行时必需的开发工具和文档,只保留了 Python 解释器和最基础的系统库,体积压缩到 120MB 左右。对于一个纯 Flask Web 应用来说,你根本不需要gccpsycopg2-binary这种预编译轮子(wheel)就能完美工作。至于ubuntu:18.04,它更底层,你需要自己apt update && apt install python3-pip python3-venv,然后自己管理 Python 版本,这完全违背了使用官方 Python 镜像的初衷——官方镜像已经为你做好了所有 Python 相关的优化和安全加固。所以,python:3.9-slim是一个完美的平衡点:它足够小,能加快镜像拉取和部署速度;它足够完整,能运行所有标准的 Python Web 应用;它足够安全,由 Python 官方团队维护,漏洞修复及时。我曾经对比过,用python:3.9构建的镜像,CI/CD 流水线里docker build步骤耗时 3 分钟,而换成slim后,降到 45 秒。对于需要频繁迭代的项目,这节省的时间就是实实在在的生产力。

2.4 为什么应用结构要遵循“配置与代码分离”原则,而不是把config.py直接写死?

Flask 官方文档里有个经典例子,config.py里定义class Config,class DevelopmentConfig(Config),class ProductionConfig(Config)。很多新手会直接把这个文件COPY进 Docker 镜像,然后在app.pyapp.config.from_object('config.ProductionConfig')。这在开发阶段没问题,但到了生产环境,就埋下了巨大的安全隐患。ProductionConfig里必然包含数据库密码、SECRET_KEY、API 密钥等敏感信息。一旦这个镜像被意外推送到公共仓库(比如你忘了设私有),或者被内部员工误操作docker save导出,这些密钥就彻底泄露了。正确的做法是:Docker 镜像里只放“可公开”的代码和配置骨架,所有敏感信息,通过环境变量(Environment Variables)在容器启动时注入。Docker 的-e参数和docker-compose.yml中的environment字段,就是为此而生。你的app.py里应该写app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'dev-key'),数据库 URL 也一样:SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL', 'sqlite:///app.db')。这样,镜像本身是干净的、可复用的、可审计的。同一个镜像,你可以在测试环境用-e DATABASE_URL=sqlite:///test.db启动,在生产环境用-e DATABASE_URL=postgresql://user:pass@db:5432/myapp启动,代码零修改。这不仅是安全最佳实践,更是 DevOps 流水线自动化的前提。CI 系统在构建镜像时,根本不需要接触任何密钥;CD 系统在部署时,才从密钥管理服务(如 HashiCorp Vault 或 AWS Secrets Manager)中拉取密钥,注入到容器环境里。所以,我们在设计之初,就必须把配置管理的逻辑想清楚,而不是等到上线前夜才手忙脚乱地改代码。

3. 核心细节解析与实操要点:从零开始搭建一个可部署的 Flask-Docker 项目

3.1 项目目录结构:一个看似简单,却决定后期维护成本的决策

很多教程教你,项目根目录下放一个app.py,一个requirements.txt,然后写个Dockerfile,完事。这在五分钟快速演示时没问题,但一旦项目增长到十几个路由、多个模型、前后端分离,这种扁平结构就会让你抓狂。我推荐一个经过生产验证的、清晰且可扩展的目录结构:

myflaskapp/ ├── app/ # Flask 应用的核心包 │ ├── __init__.py # 创建 Flask 实例,初始化扩展(SQLAlchemy, CORS) │ ├── models.py # 数据库模型定义 │ ├── routes.py # 所有业务路由(蓝图 Blueprints 的集合) │ └── config.py # 配置类定义(不放密钥!只放默认值和结构) ├── migrations/ # SQLAlchemy 迁移脚本(如果用 Flask-Migrate) ├── tests/ # 单元测试和集成测试 ├── requirements.txt # 生产环境依赖(精简,只含必要包) ├── requirements-dev.txt # 开发环境依赖(含 pytest, flake8 等) ├── Dockerfile # 构建应用镜像 ├── docker-compose.yml # 定义多服务(app + db + nginx?) └── .dockerignore # 告诉 Docker 构建时忽略哪些文件

这个结构的关键在于app/是一个 Python 包(有__init__.py),而不是一个普通文件夹。这意味着你可以用from app import create_app来导入应用工厂函数,而不是from app import app。应用工厂模式(Application Factory)是 Flask 官方推荐的大型项目组织方式,它让你能在不同环境下(开发、测试、生产)创建不同的应用实例,每个实例拥有独立的配置和扩展。app/__init__.py里的核心代码通常是这样的:

from flask import Flask from flask_sqlalchemy import SQLAlchemy from flask_cors import CORS # 创建扩展实例,但不绑定到具体应用 db = SQLAlchemy() cors = CORS() def create_app(config_name='production'): app = Flask(__name__) # 根据 config_name 加载配置 if config_name == 'development': app.config.from_object('app.config.DevelopmentConfig') else: app.config.from_object('app.config.ProductionConfig') # 初始化扩展 db.init_app(app) cors.init_app(app) # 注册蓝图 from app.routes import main_bp app.register_blueprint(main_bp) return app

提示:create_app函数是整个架构的入口。Docker 容器启动时,Gunicorn 或 uWSGI 就是调用这个函数来获取一个应用实例。它不依赖全局变量app,因此可以被多次调用,非常适合测试和多环境部署。

3.2requirements.txt的精炼之道:如何避免“包地狱”

pip freeze > requirements.txt是新手最爱的命令,但它会把你开发环境里所有的包,包括pip,setuptools,wheel甚至jupyter都导出来。这会导致两个严重问题:一是镜像体积无谓增大;二是引入了不必要的、甚至可能冲突的依赖。一个健康的requirements.txt应该只包含你的应用直接依赖的第三方库。例如,你的app/routes.pyimport requests,那么requests就必须在requirements.txt里;但如果你只是在某个临时脚本里用了pandas,而主应用完全不碰它,那就坚决不能放进去。更进一步,你应该使用pip-tools这个工具来管理依赖。它的工作流是:先写一个requirements.in,里面只放顶层依赖:

# requirements.in Flask==2.3.3 Flask-SQLAlchemy==3.0.5 Flask-CORS==4.0.0 gunicorn==21.2.0 psycopg2-binary==2.9.7

然后运行pip-compile requirements.in,它会自动解析所有传递依赖,并生成一个带精确版本号的requirements.txt。这样做的好处是:可重现性。今天pip-compile生成的requirements.txt,和三个月后在另一台机器上运行同样的命令,生成的结果完全一致。你再也不用担心pip install -r requirements.txt时,因为某个包发布了新补丁版本(比如flask 2.3.4),导致线上行为发生微妙变化。我见过最惨的一次,就是因为没锁版本,flask2.2.x升到2.3.xbefore_request钩子的执行顺序变了,导致一个关键的权限校验中间件失效,线上服务瘫痪了 17 分钟。所以,requirements.txt不是清单,而是契约。它承诺:只要用这个文件,构建出来的环境,就和我本地测试过的环境,一模一样。

3.3Dockerfile的逐行剖析:每一行都是一个经验教训

下面是一个为 Ubuntu 18.04 量身定制的、生产就绪的Dockerfile,我会逐行解释其背后的设计意图:

# 第1行:基础镜像,明确指定 Python 版本和 slim 变体 FROM python:3.9-slim # 第2行:设置工作目录,所有后续 COPY 和 RUN 命令都在此目录下执行 WORKDIR /app # 第3行:复制 requirements.txt,这是为了利用 Docker 的构建缓存机制 # 如果 requirements.txt 没变,这一步和下一行 pip install 都会直接用缓存,极大加速构建 COPY requirements.txt . # 第4行:安装生产依赖。--no-cache-dir 避免在镜像里留下 pip 缓存,减小体积 # -q 是 quiet 模式,减少日志噪音 RUN pip install --no-cache-dir -q -r requirements.txt # 第5行:复制应用源码。注意,这里只复制源码,不复制测试、文档等无关文件 # 这也是为什么需要 .dockerignore 文件 COPY app/ . # 第6行:暴露端口。这只是声明,告诉使用者这个容器会监听哪个端口 # 真正的端口映射是在 docker run 时用 -p 参数完成的 EXPOSE 8000 # 第7行:定义启动命令。这里用 gunicorn 作为 WSGI 服务器,比 Flask 自带的开发服务器稳定得多 # --bind 0.0.0.0:8000 表示监听所有网络接口的 8000 端口 # --workers 4 是根据 CPU 核心数的经验值(2*CPU+1),Ubuntu 18.04 虚拟机通常 2-4 核 # --access-logfile - 将访问日志输出到 stdout,方便 Docker logs 查看 # --error-logfile - 同理,错误日志也输出到 stdout # app:create_app() 是调用应用工厂函数,括号里的 () 表示要执行它 CMD ["gunicorn", "--bind", "0.0.0.0:8000", "--workers", "4", "--access-logfile", "-", "--error-logfile", "-", "app:create_app()"]

注意:CMD指令必须用 JSON 数组格式(["gunicorn", ...]),而不是 shell 格式(gunicorn ...)。因为后者会启动一个/bin/sh -c的 shell 进程作为 PID 1,而 Docker 要求 PID 1 进程能正确处理 Unix 信号(如 SIGTERM),以便优雅关闭。gunicorn本身就能很好地处理信号,但 shell 不能。这是一个极其隐蔽、但会导致容器无法正常停止的致命错误。我第一次遇到时,docker stop命令要等 10 秒超时后才强行 kill,日志里全是Killed,查了整整一天才发现是 CMD 格式错了。

3.4.dockerignore:一个被严重低估的“性能加速器”

.dockerignore文件的作用,和.gitignore类似,但它影响的是 Docker 构建过程。如果你不写这个文件,Docker 在执行docker build时,会把当前目录下的所有文件(包括.git文件夹、__pycache__venvnode_modules、大型日志文件)都打包发送给 Docker daemon。这不仅浪费网络带宽,更会破坏 Docker 的构建缓存。因为只要.git文件夹里有一个 commit 变了,整个上下文(context)的哈希值就变了,Docker 就认为这是一个全新的构建,之前的缓存全部失效。一个标准的.dockerignore应该长这样:

.git .gitignore __pycache__ *.pyc *.pyo *.pyd .Python env/ venv/ .venv/ pip-log.txt pip-delete-this-directory.txt .tox .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover *.log .DS_Store .dockerignore README.md requirements-dev.txt tests/ migrations/

这份列表的意义在于:它把所有与“运行时”无关的文件都排除在外tests/migrations/虽然属于项目,但它们不会被app/里的代码直接 import,所以没必要放进生产镜像。requirements-dev.txt里可能有pytest,但生产镜像里永远不需要跑测试。把这些文件排除后,docker build的上下文体积可能从几百 MB 直接降到几 MB,构建时间从 2 分钟缩短到 15 秒。这不是微优化,而是工程效率的基石。

4. 实操过程与核心环节实现:在 Ubuntu 18.04 上一步步完成从零到部署

4.1 环境准备:在干净的 Ubuntu 18.04 上安装 Docker Engine

我们假设你有一台全新的 Ubuntu 18.04 Server 虚拟机,SSH 登录后,第一步是安装 Docker。切记,不要用sudo apt install docker.io,因为 Ubuntu 官方仓库里的docker.io版本太老(18.09),不支持一些新特性,且安全更新滞后。我们必须使用 Docker 官方的 APT 仓库。以下是经过反复验证的、在 Ubuntu 18.04 上最稳定的安装步骤:

# 1. 更新 apt 包索引 sudo apt update # 2. 安装必要的系统工具,用于通过 HTTPS 添加仓库 sudo apt install -y apt-transport-https ca-certificates curl software-properties-common # 3. 添加 Docker 的官方 GPG 密钥 curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - # 4. 添加 stable 仓库(注意:Ubuntu 18.04 的 codename 是 bionic) echo "deb [arch=amd64] https://download.docker.com/linux/ubuntu bionic stable" | sudo tee /etc/apt/sources.list.d/docker.list # 5. 再次更新 apt 索引,这次会包含 Docker 仓库 sudo apt update # 6. 安装 Docker Engine(不是 docker.io!) sudo apt install -y docker-ce docker-ce-cli containerd.io # 7. 验证安装是否成功 sudo docker --version # 输出应为:Docker version 20.10.x, build xxxxx # 8. 将当前用户加入 docker 组,避免每次都要 sudo sudo usermod -aG docker $USER # 9. 重要!重启 docker 服务,并重新登录用户(或执行 newgrp docker) sudo systemctl restart docker # 然后退出 SSH,重新登录,或者执行: newgrp docker

提示:第 8 步和第 9 步是关键。如果不把用户加入docker组,你每次运行docker命令都得加sudo,这不仅麻烦,更是一种安全风险(sudo docker等价于获得 root 权限)。newgrp docker命令会启动一个新的 shell 会话,加载新的组权限,比退出重登更快捷。执行完后,运行docker ps,如果返回一个空列表(而不是 permission denied 错误),就说明一切正常。

4.2 创建一个最小但完整的 Flask 应用:hello-world的工业级写法

现在,我们来创建一个真正的、可部署的 Flask 应用,而不是教科书式的app.run()。在你的家目录下,创建项目文件夹:

mkdir -p ~/myflaskapp/{app,migrations,tests} cd ~/myflaskapp

然后,创建app/__init__.py

# app/__init__.py import os from flask import Flask from flask_sqlalchemy import SQLAlchemy from flask_cors import CORS db = SQLAlchemy() cors = CORS() def create_app(config_name=None): app = Flask(__name__) # 从环境变量读取配置,如果没有,则用默认值 app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'dev-secret-key-change-in-prod') app.config['SQLALCHEMY_DATABASE_URI'] = os.environ.get('DATABASE_URL', 'sqlite:///app.db') app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False # 初始化扩展 db.init_app(app) cors.init_app(app) # 创建一个简单的健康检查路由 @app.route('/health') def health(): return {'status': 'ok', 'message': 'Flask app is running'} # 注册主蓝图 from app.routes import main_bp app.register_blueprint(main_bp) return app

接着,创建app/routes.py

# app/routes.py from flask import Blueprint, jsonify main_bp = Blueprint('main', __name__) @main_bp.route('/') def index(): return jsonify({ 'message': 'Welcome to my Flask App!', 'version': '1.0.0', 'environment': 'production' }) @main_bp.route('/api/books') def get_books(): # 这里将来会查询数据库,现在先返回模拟数据 return jsonify([ {'id': 1, 'title': 'Docker Deep Dive', 'author': 'Nigel Poulton'}, {'id': 2, 'title': 'Flask Web Development', 'author': 'Miguel Grinberg'} ])

然后,创建app/config.py(虽然我们不直接用它,但留着结构):

# app/config.py class Config: SECRET_KEY = os.environ.get('SECRET_KEY') or 'hard-to-guess-string' class DevelopmentConfig(Config): DEBUG = True SQLALCHEMY_DATABASE_URI = os.environ.get('DEV_DATABASE_URL') or 'sqlite:///dev-app.db' class ProductionConfig(Config): SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or 'sqlite:///prod-app.db' config = { 'development': DevelopmentConfig, 'production': ProductionConfig, 'default': ProductionConfig }

最后,创建requirements.txt

Flask==2.3.3 Flask-SQLAlchemy==3.0.5 Flask-CORS==4.0.0 gunicorn==21.2.0 psycopg2-binary==2.9.7

4.3 构建、运行与验证:让第一个容器真正跑起来

现在,所有代码都准备好了。让我们进入项目根目录,执行构建命令:

cd ~/myflaskapp # 构建镜像,-t 是 tag,给镜像起个名字 docker build -t myflaskapp . # 查看构建好的镜像 docker images | grep myflaskapp # 运行容器,-p 8000:8000 将宿主机的 8000 端口映射到容器的 8000 端口 # -d 是后台运行(detached mode) # --name 给容器起个好记的名字 docker run -d -p 8000:8000 --name myflaskapp-container myflaskapp # 查看容器是否在运行 docker ps | grep myflaskapp # 查看容器日志,确认 gunicorn 启动成功 docker logs myflaskapp-container # 你应该看到类似:[INFO] Starting gunicorn 21.2.0 # [INFO] Listening at: http://0.0.0.0:8000 (1) # [INFO] Using worker: sync # 用 curl 测试 API curl http://localhost:8000/health # 返回:{"status":"ok","message":"Flask app is running"} curl http://localhost:8000/api/books # 返回一个 JSON 数组

提示:如果curl返回Connection refused,首先检查docker ps是否显示容器状态为Up X seconds;其次检查docker logs里是否有ImportErrorOperationalError。最常见的错误是gunicorn找不到app:create_app(),这通常是因为COPY app/ .这一步没把app/文件夹正确复制进去,或者app/下缺少__init__.py。用docker exec -it myflaskapp-container ls -l /app进入容器内部查看文件结构,是最快的排查方法。

4.4 使用docker-compose管理多服务:为 Flask 应用添加 PostgreSQL 数据库

单个 Flask 容器是玩具,真正的应用需要数据库。docker-compose是管理多容器应用的瑞士军刀。创建docker-compose.yml

# docker-compose.yml version: '3.8' services: # Flask 应用服务 web: build: . ports: - "8000:8000" environment: - SECRET_KEY=your-super-secret-key-here - DATABASE_URL=postgresql://postgres:mysecretpassword@db:5432/myflaskdb depends_on: - db # 重启策略:总是重启,确保服务高可用 restart: always # PostgreSQL 数据库服务 db: image: postgres:13 environment: - POSTGRES_DB=myflaskdb - POSTGRES_USER=postgres - POSTGRES_PASSWORD=mysecretpassword volumes: # 将数据库数据持久化到宿主机的 /var/lib/postgresql/data 目录 - ./postgres-data:/var/lib/postgresql/data restart: always # 可选:Nginx 反向代理(为生产环境准备) # nginx: # image: nginx:alpine # ports: # - "80:80" # volumes: # - ./nginx.conf:/etc/nginx/nginx.conf # depends_on: # - web

现在,启动整个栈:

# 在 docker-compose.yml 所在目录执行 docker-compose up -d # 查看所有服务状态 docker-compose ps # 查看 web 服务的日志 docker-compose logs -f web # 查看 db 服务的日志 docker-compose logs -f db

注意:docker-compose up -d会自动构建web服务(因为build: .),并拉取postgres:13镜像。depends_on确保db服务先启动,但depends_on并不等待db服务“准备好”(即数据库监听端口),它只等待容器进程启动。所以,你的 Flask 应用启动时,可能会遇到psycopg2.OperationalError: could not connect to server。解决方案是:在app/__init__.pycreate_app函数里,加入一个简单的重试逻辑,或者使用wait-for-it.sh脚本。但更优雅的方式是,在web服务的CMD里,用一个包装脚本先ping数据库,再启动 gunicorn。不过,对于大多数场景,depends_on加上restart: always已经足够健壮——第一次连接失败,gunicorn 进程崩溃,Docker 会根据restart: always策略立即重启它,第二次通常就成功了。

5. 常见问题与排查技巧实录:那些只有亲手部署过才会懂的“坑”

5.1 问题速查表:高频故障与一招制敌的解决方案

问题现象根本原因快速诊断命令一招制敌的解决方案
docker: command not foundDocker 未安装,或用户未加入dockerwhich docker
groups
重新执行sudo usermod -aG docker $USERnewgrp docker
Cannot connect to the Docker daemonDocker 服务未运行sudo systemctl status dockersudo systemctl start docker
sudo systemctl enable docker(开机自启)
ERROR: failed to solve: rpc error: code = Unknown desc = executor failed running...Dockerfile 中某条RUN命令执行失败(如pip install报错)docker build -t test .(去掉-q参数,看详细日志)检查requirements.txt里的包名和版本是否拼写正确;检查网络是否能访问 PyPI
ImportError: No module named 'app'COPY命令没把app/文件夹正确复制,或app/下缺少__init__.pydocker run -it myflaskapp ls -l /app进入容器,确认/app目录下有__init__.pyroutes.py等文件
OSError: [Errno 98] Address already in use宿主机的 8000 端口已被占用sudo lsof -i :8000
`sudo netstat -tulpn
grep :8000`
psycopg2.OperationalError: could not connect to serverFlask 容器启动太快,PostgreSQL 容器还没准备好docker-compose logs db
docker-compose exec db psql -U postgres -c '\l'
web服务的CMD前加一个sleep 10,或使用wait-for-it.sh脚本

5.2 “无法解析导入 ‘

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询