欢迎光临
我们一直在努力

Docker 实战部署:从本地镜像到云服务器,一篇走通 FastAPI + Celery + MySQL + Redis + Nginx

Docker 实战部署:从本地镜像到云服务器,一篇走通 FastAPI + Celery + MySQL + Redis + Nginx

一、前言

最近在部署自己的一个实际项目时,我把原本散落在本地开发环境中的 FastAPI、Celery、MySQL、Redis 等服务逐步 Docker 化,并最终部署到了云服务器。

整个过程走下来,我发现 Docker 部署真正麻烦的地方并不是记住几个 docker 命令,而是理解下面这条完整链路:

本地开发
↓
Docker Build
↓
Docker Image
↓
镜像仓库
↓
云服务器 Pull
↓
Docker Compose
↓
FastAPI / Celery / MySQL / Redis
↓
Nginx
↓
域名
↓
HTTPS

尤其第一次真正把一个多服务项目部署到服务器时,很容易遇到这些问题:

  • Docker 镜像到底是什么?
  • 本地已经 Build 成功,怎么放到服务器?
  • 要不要把源码传到服务器重新 Build?
  • Docker Compose 到服务器以后应该怎么改?
  • MySQL 和 Redis 为什么不能写 localhost?
  • 为什么服务器执行 docker compose up –build 特别慢?
  • Docker 服务运行在 8000 端口,怎么绑定自己的域名?
  • 数据库容器重新创建以后,数据会不会全部消失?
  • Docker 镜像仓库到底解决了什么问题?

本文就根据一次真实部署过程,把这些问题完整串起来。

本文项目使用的主要技术栈如下:

Backend
├── Python 3.12
├── FastAPI
├── Uvicorn
├── SQLAlchemy
├── Alembic
└── Celery

Infrastructure
├── MySQL 8.4
├── Redis 7.4
├── Docker
├── Docker Compose
└── Nginx

Registry
└── Huawei Cloud SWR

最终希望达到的效果非常简单:

本地开发完成后构建 Docker 镜像,将镜像推送到镜像仓库,服务器只需要 Pull 镜像并启动,不再手动安装 Python、项目依赖、Celery 等运行环境。


二、为什么这次决定使用 Docker?

如果不用 Docker,一个 Python 项目部署到服务器,大概需要经历:

安装 Python
↓
创建虚拟环境
↓
安装 pip 依赖
↓
配置 MySQL
↓
安装 Redis
↓
配置 FastAPI
↓
配置 Celery Worker
↓
配置 Celery Beat
↓
使用 systemd / Supervisor 守护进程
↓
配置 Nginx

如果只有一个 FastAPI 服务还好。

但我的项目实际上已经包含多个进程:

API
Worker
Dispatcher
Scheduler
Migration
MySQL
Redis

如果全部直接部署在 Linux 上,就需要分别考虑每个服务如何启动、停止、重启以及开机自启。

而 Docker Compose 可以把这些东西统一起来:

docker compose up -d

查看状态:

docker compose ps

查看日志:

docker compose logs -f

停止:

docker compose down

这也是我最终决定将整个项目 Docker 化的重要原因。


三、项目的 Docker Compose 架构

项目不是单容器结构,而是一个典型的多服务应用。

整体关系大致如下:

┌─────────────────┐
│ Nginx │
│ 80 / 443 │
└────────┬────────┘
│
▼
┌─────────────────┐
│ FastAPI │
│ :8000 │
└────────┬────────┘
│
┌────────────┴────────────┐
│ │
▼ ▼
┌───────────────┐ ┌───────────────┐
│ MySQL │ │ Redis │
│ :3306 │ │ :6379 │
└───────────────┘ └───────┬───────┘
│
┌────────────────┼────────────────┐
▼ ▼ ▼
Worker Dispatcher Scheduler

本地使用的 Compose 核心结构如下:

services:
api:
image: ${SERVER_IMAGE:–writing–assistant–server:latest}
build:
context: ..
dockerfile: server/Dockerfile
env_file: .env
command: uvicorn backend.main:app ––host 0.0.0.0 ––port 8000
depends_on:
migrate:
condition: service_completed_successfully
ports:
– "8000:8000"
volumes:
– server–storage:/data/storage
restart: unless–stopped

worker:
image: ${SERVER_IMAGE:–writing–assistant–server:latest}
env_file: .env
command: >
celery -A backend.workers.celery_app:celery_app
worker –loglevel=INFO –concurrency=1 -Q publish,celery

depends_on:
migrate:
condition: service_completed_successfully
redis:
condition: service_healthy
shm_size: 1gb
init: true
volumes:
– server–storage:/data/storage
restart: unless–stopped

dispatcher:
image: ${SERVER_IMAGE:–writing–assistant–server:latest}
env_file: .env
command: >
celery -A backend.workers.celery_app:celery_app
worker –loglevel=INFO –concurrency=1 -Q scheduler

depends_on:
migrate:
condition: service_completed_successfully
redis:
condition: service_healthy
restart: unless–stopped

scheduler:
image: ${SERVER_IMAGE:–writing–assistant–server:latest}
env_file: .env
command: >
celery -A backend.workers.celery_app:celery_app
beat –loglevel=INFO

depends_on:
migrate:
condition: service_completed_successfully
redis:
condition: service_healthy
volumes:
– server–storage:/data/storage
restart: unless–stopped

migrate:
image: ${SERVER_IMAGE:–writing–assistant–server:latest}
env_file: .env
command: alembic upgrade head
depends_on:
mysql:
condition: service_healthy
restart: "no"

mysql:
image: mysql:8.4
environment:
MYSQL_DATABASE: ${MYSQL_DATABASE}
MYSQL_USER: ${MYSQL_USER}
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
volumes:
– mysql–data:/var/lib/mysql
healthcheck:
test:
[
"CMD-SHELL",
"mysqladmin ping -h localhost -uroot -p$$MYSQL_ROOT_PASSWORD || exit 1"
]
interval: 10s
timeout: 5s
retries: 12
start_period: 30s
restart: unless–stopped

redis:
image: redis:7.4–alpine
command: redis–server ––appendonly yes
volumes:
– redis–data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 12
restart: unless–stopped

volumes:
mysql-data:
redis-data:
server-storage:

这里有一个很重要的设计:

api
worker
dispatcher
scheduler
migrate

虽然是五个不同的服务,但实际上完全可以使用同一个业务 Docker Image。

区别只是每个 Container 启动时执行的 command 不一样。

例如:

writing-assistant-server
│
├── uvicorn → API
├── celery worker → Worker
├── celery worker -Q scheduler → Dispatcher
├── celery beat → Scheduler
└── alembic upgrade head → Migration

这也是 Docker 中非常常见的一种设计。


四、本地构建 Docker 镜像

本地执行:

docker compose build

或者:

docker compose up –build -d

构建完成以后:

docker images

可以看到类似:

REPOSITORY TAG IMAGE ID SIZE
writing-assistant-server latest 05f630b4a28e 3.23GB
mysql 8.4 0744ee5ef89c 1.1GB
redis 7.4-alpine 858f009f9709 58.2MB

其中:

writing-assistant-server:latest

就是我们自己的业务镜像。

这里需要注意:

Docker Image 和 Docker Container 并不是同一个概念。

可以简单理解为:

Dockerfile
↓
docker build
↓
Image
↓
docker run
↓
Container

Image 更像一个只读模板。

Container 则是 Image 的运行实例。

同一个 Image 完全可以启动多个 Container。

这也是为什么前面的:

API
Worker
Dispatcher
Scheduler
Migration

可以共用 writing-assistant-server。


五、本地镜像怎么传到服务器?

这里主要有三种方案。

5.1 方案一:使用 Docker Registry

这是我最终采用的方式。

流程:

Mac / 开发机
↓
docker build
↓
Docker Image
↓
docker push
↓
Huawei Cloud SWR
↓
docker pull
↓
Production Server

例如镜像仓库地址:

swr.cn-east-3.myhuaweicloud.com/example/writing-assistant-server

服务器可以直接:

docker pull \\
swr.cn-east-3.myhuaweicloud.com/example/writing-assistant-server:latest

生产环境更推荐使用明确版本:

docker pull \\
swr.cn-east-3.myhuaweicloud.com/example/writing-assistant-server:v1.0.0


5.2 方案二:docker save + SCP

Docker 镜像并不是必须上传 Registry。

可以直接导出:

docker save writing-assistant-server:latest \\
| gzip > writing-assistant-server.tar.gz

然后:

scp writing-assistant-server.tar.gz \\
root@SERVER_IP:/opt/writing-assistant/

服务器导入:

gunzip -c writing-assistant-server.tar.gz | docker load

然后:

docker images

即可看到:

writing-assistant-server latest

这种方式比较适合:

  • 临时部署
  • 内网服务器
  • 无镜像仓库环境
  • 偶尔发布

但如果频繁发布,Registry 会舒服很多。


5.3 方案三:服务器直接 Build

还有一种最直接的方式:

git clone ...
cd project
docker compose up –build -d

也就是:

Git Repository
↓
Production Server
↓
Docker Build
↓
Docker Image
↓
Container

虽然能用,但这次部署过程中我发现:

对正式服务器来说,这通常不是最理想的方式。

原因后面会详细讲。


六、服务器 Compose 和本地 Compose 不应该完全一样

本地开发时需要:

build:
context: ..
dockerfile: server/Dockerfile

因为本地需要从源码 Build。

但是服务器如果已经从 Registry 获取构建好的 Image,就没有必要再 Build。

因此生产 Compose 更推荐:

services:
api:
image: ${SERVER_IMAGE}
env_file: .env
command: uvicorn backend.main:app ––host 0.0.0.0 ––port 8000

.env:

SERVER_IMAGE=swr.cn-east-3.myhuaweicloud.com/example/writing-assistant-server:v1.0.0

然后:

docker compose pull
docker compose up -d

这两个命令非常关键。

正确的生产部署链路应该是:

开发机 / CI
│
│ Build
▼
Docker Image
│
│ Push
▼
Registry
│
│ Pull
▼
Production
│
│ Run
▼
Container

而不是:

Production
↓
Clone Source
↓
Download Dependencies
↓
Build
↓
Run

换句话说:

Build 和 Run 最好分开。


七、为什么我在服务器执行 Docker Build 慢得离谱?

这次实际部署时,我直接在服务器执行了:

docker compose up –build -d

结果发现 Build 非常慢。

日志中出现:

[5/8] RUN pip install …
772.5s

一个步骤就执行了 772 秒。

进一步看:

4.5/4.5 MB 17.9 kB/s 0:04:06

一个只有 4.5MB 左右的 Python 包,竟然下载了 4 分钟。

这时候问题其实已经非常明显:

不是 Docker Build 本身慢,而是 pip 下载 Python Dependencies 太慢。

整个过程其实是:

docker build
↓
python:3.12-bookworm
↓
pip install
↓
PyPI
↓
网络速度极慢
↓
整个 Docker Build 被阻塞

这也是为什么:

Docker Build ≠ 单纯编译代码

Dockerfile 中所有:

RUN …

都会真实执行。

如果里面有:

RUN pip install …

就需要真实访问 PyPI 下载依赖。


八、为什么生产服务器最好不要每次重新 Build?

这次经历以后,这个问题就非常直观了。

假设服务器每次发布都:

docker compose up –build -d

那么服务器需要承担:

拉基础镜像
下载系统依赖
下载 Python 依赖
构建镜像
占用 CPU
占用 RAM
占用 Disk

如果服务器网络访问 PyPI 又比较慢,部署一次可能十几分钟甚至几十分钟。

而如果提前 Build:

开发机
↓
Build Once
↓
Registry
↓
Server Pull

服务器只负责:

下载 Image
启动 Container

这也是容器镜像非常重要的价值:

把应用程序和运行环境一起打包成一个可分发的部署产物。


九、Docker Build Cache 也非常重要

Docker 并不是每次都从头 Build。

例如 Dockerfile:

COPY server/pyproject.toml /workspace/server/pyproject.toml

RUN pip install …

只要:

pyproject.toml

没有发生变化,Docker 就可能直接复用之前的 Layer:

CACHED

流程相当于:

pyproject.toml 没变
↓
依赖没有变化
↓
pip install Layer 继续使用
↓
只重新 COPY 新代码

但是如果修改:

pyproject.toml

这一层 Cache 就会失效:

pyproject.toml changed
↓
pip install cache invalid
↓
重新安装全部 dependencies

因此 Dockerfile 的 Layer 顺序实际上也会直接影响构建效率。


十、Docker Compose 内部为什么不能乱写 localhost?

这是第一次部署 Docker 多服务项目非常容易踩的坑。

假设 FastAPI 的数据库配置写:

MYSQL_HOST=localhost

在 Docker Compose 中往往是错的。

因为:

FastAPI Container

里面的:

localhost

代表的是:

FastAPI Container 自己

而不是 MySQL Container。

正确结构:

FastAPI Container
│
│ mysql:3306
▼
MySQL Container

因此应该:

MYSQL_HOST=mysql
MYSQL_PORT=3306

Redis 同理:

REDIS_HOST=redis
REDIS_PORT=6379

因为 Compose 中:

services:
mysql:
…

redis:
…

服务名本身就可以作为内部 DNS Hostname。

因此:

mysql:3306
redis:6379

就是容器之间的通信地址。


十一、depends_on 不等于服务真正可用了

例如:

depends_on:
mysql:
condition: service_healthy

为什么需要:

service_healthy

而不是单纯“先启动 MySQL”?

因为:

Container Started

并不意味着:

MySQL Ready

MySQL 容器启动后还需要初始化数据库、用户、权限等。

因此应该设置 Health Check:

healthcheck:
test:
[
"CMD-SHELL",
"mysqladmin ping -h localhost -uroot -p$$MYSQL_ROOT_PASSWORD || exit 1"
]
interval: 10s
timeout: 5s
retries: 12
start_period: 30s

这样整个启动链路可以变成:

MySQL Start
↓
MySQL Health Check
↓
Healthy
↓
Alembic Migration
↓
Migration Success
↓
FastAPI / Worker / Scheduler Start

比所有服务同时启动稳定很多。


十二、为什么 Migration 单独做一个 Container?

我的 Compose 中还有一个:

migrate:
image: ${SERVER_IMAGE}
command: alembic upgrade head

它专门执行:

alembic upgrade head

成功后直接退出。

因此看到:

migrate Exited (0)

并不是错误。

反而意味着:

Migration completed successfully

真正有问题的是:

Exited (1)

这时候可以:

docker compose logs migrate

查看数据库 Migration 为什么失败。

这种设计还有一个好处:

应用启动和数据库结构升级之间存在明确顺序。

不会出现:

FastAPI 已经启动
↓
开始接受请求
↓
数据库表还没升级
↓
SQL Error


十三、Volume:容器删了为什么数据还在?

数据库绝对不能直接把重要数据存在 Container 可写层里。

因此 Compose 中使用:

volumes:
– mysql–data:/var/lib/mysql

Redis:

volumes:
– redis–data:/data

业务文件:

volumes:
– server–storage:/data/storage

底部声明:

volumes:
mysql-data:
redis-data:
server-storage:

整个关系:

Container
│
│ Mount
▼
Docker Volume

所以:

Container
可以被删除、更新、重新创建

Volume
继续保存数据

这就是为什么:

docker compose pull
docker compose up -d

重新创建业务 Container 时,数据库不会因此自动清空。

但是一定要注意:

docker compose down -v

其中:

-v

代表同时删除相关 Volume。

生产环境执行这个命令之前一定要确认自己到底在做什么。

同时:

Volume 不是 Backup。

生产数据库仍然需要:

mysqldump
↓
Backup File
↓
Object Storage / Other Server

否则服务器磁盘损坏以后,Volume 一样救不了数据。


十四、FastAPI 运行在 8000,怎么绑定域名?

Docker 启动以后:

ports:
– "8000:8000"

意味着:

Server :8000
↓
Container :8000

理论上可以:

http://SERVER_IP:8000

直接访问。

但是生产环境一般不建议这么做。

更推荐:

Internet
↓
Domain
↓
Nginx :80 / :443
↓
127.0.0.1:8000
↓
Docker
↓
FastAPI :8000

因此 Compose 可以改成:

ports:
– "127.0.0.1:8000:8000"

这里非常关键。

它意味着:

127.0.0.1:8000

只监听服务器本机。

外部用户无法直接访问:

SERVER_IP:8000

只能通过 Nginx。


十五、配置 Nginx 反向代理

假设域名:

api.example.com

FastAPI:

127.0.0.1:8000

Nginx 可以:

server {
listen 80;
listen [::]:80;

server_name api.example.com;

location / {
proxy_pass http://127.0.0.1:8000;

proxy_http_version 1.1;

proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;

proxy_connect_timeout 60s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;
}
}

检查:

nginx -t

如果输出:

syntax is ok
test is successful

重新加载:

systemctl reload nginx

最终:

http://api.example.com
↓
Nginx
↓
127.0.0.1:8000
↓
Docker
↓
FastAPI


十六、为什么 MySQL 和 Redis 不应该暴露公网?

Compose 中我没有:

ports:
– "3306:3306"

也没有:

ports:
– "6379:6379"

这是故意的。

因为 FastAPI、Celery、MySQL、Redis 都处于 Compose Network 中。

它们可以直接:

FastAPI → mysql:3306
FastAPI → redis:6379
Worker → redis:6379

因此根本不需要:

Internet
↓
3306

Internet
↓
6379

公网真正需要暴露的通常只有:

80
443

甚至 API 的:

8000

都只监听:

127.0.0.1

这样整体暴露面会小很多。


十七、再进一步:配置 HTTPS

Nginx 反向代理完成以后,还可以使用 Let’s Encrypt + Certbot。

Ubuntu:

sudo apt install certbot python3-certbot-nginx -y

申请:

sudo certbot –nginx -d api.example.com

最终访问:

https://api.example.com

整个生产链路就变成:

Internet
│
│ HTTPS :443
▼
┌─────────────┐
│ Nginx │
└──────┬──────┘
│
127.0.0.1:8000
│
▼
┌─────────────┐
│ FastAPI │
└──────┬──────┘
│
┌────────────┴────────────┐
│ │
▼ ▼
┌─────────┐ ┌─────────┐
│ MySQL │ │ Redis │
└─────────┘ └────┬────┘
│
┌─────────┼─────────┐
▼ ▼ ▼
Worker Dispatcher Scheduler


十八、以后发布新版本应该怎么做?

假设:

v1.0.0

已经在线。

开发完成:

v1.0.1

在开发机或 CI 构建:

docker build -t \\
swr.cn-east-3.myhuaweicloud.com/example/writing-assistant-server:v1.0.1 \\
.

Push:

docker push \\
swr.cn-east-3.myhuaweicloud.com/example/writing-assistant-server:v1.0.1

服务器修改:

SERVER_IMAGE=swr.cn-east-3.myhuaweicloud.com/example/writing-assistant-server:v1.0.1

然后:

docker compose pull
docker compose up -d

完成更新。

如果:

v1.0.1

发现严重 Bug,可以改回:

SERVER_IMAGE=swr.cn-east-3.myhuaweicloud.com/example/writing-assistant-server:v1.0.0

重新:

docker compose up -d

这也是为什么生产环境我更推荐:

v1.0.0
v1.0.1
v1.1.0

而不是永远:

latest

明确版本号会让部署记录和回滚清晰很多。


十九、Docker 部署到底有什么优势?

经过这次完整部署以后,我认为 Docker 对这种项目最大的价值不是“看起来专业”,而是下面几点。

19.1 环境一致

开发环境已经运行成功的 Image:

Python
Python Dependencies
System Dependencies
Application

整体搬到服务器。

减少:

本地能跑
服务器不能跑

这种经典问题。


19.2 多服务管理简单

原来:

FastAPI
Celery Worker
Celery Beat
Redis
MySQL

可能需要多个 systemd / Supervisor 配置。

Docker Compose 后统一:

docker compose up -d


19.3 部署产物标准化

以前交付:

Source Code
README
Python Version
requirements
Linux Dependency
Startup Script

现在交付:

Docker Image

部署目标变得更加明确。


19.4 回滚方便

v1.0.0
↓
v1.0.1
↓
出现问题
↓
v1.0.0

不需要重新修改服务器代码。


19.5 更容易做 CI/CD

以后完全可以:

Git Push
↓
GitHub Actions / GitLab CI
↓
Docker Build
↓
Docker Push
↓
Deployment

开发完成以后自动完成镜像构建。


二十、Docker 也不是没有缺点

这次部署过程中也能明显感受到一些问题。

首先是磁盘。

例如业务镜像:

3.23GB

MySQL:

1.1GB

随着版本越来越多:

v1.0.0
v1.0.1
v1.0.2
v1.1.0

服务器可能留下很多旧 Image。

可以查看:

docker system df

清理不用的 Image:

docker image prune

其次是排错复杂度。

以前:

Application
↓
Linux

现在:

Application
↓
Container
↓
Docker Network
↓
Volume
↓
Docker Engine
↓
Linux

因此需要理解:

Image
Container
Network
Volume
Port Mapping
Health Check
Compose

这些概念。

但对于多服务项目来说,这部分学习成本通常是值得的。


二十一、这次部署以后,我对 Docker 的理解发生了什么变化?

以前学习 Docker 时,很容易把重点放在:

docker build
docker run
docker ps
docker exec
docker logs

这些命令本身。

真正做完一次完整部署以后,我认为 Docker 最重要的其实不是这些命令,而是:

把应用程序构建成一个标准化、可重复部署、可版本化的运行产物。

完整的软件交付过程应该逐渐变成:

Development
│
▼
Source
│
▼
Docker Build
│
▼
Docker Image
│
▼
Registry
│
▼
Production
│
▼
Docker Compose
│
┌─────────┼─────────┐
▼ ▼ ▼
API Worker Scheduler
│ │
└─────────┬──────────┘
▼
MySQL / Redis
│
▼
Nginx
│
▼
Domain / HTTPS

这才是 Docker 真正在项目部署中的价值。


二十二、下一步:为什么我开始想自己做一个 DevOps 平台?

当这套流程真正跑通以后,我又发现一个新的问题。

虽然 Docker 已经方便很多,但每部署一个项目,我还是需要:

ssh root@server

docker login ...
docker pull ...

nano docker-compose.yml

docker compose up -d

docker compose ps

docker compose logs -f

nano /etc/nginx/...

nginx -t

systemctl reload nginx

certbot ...

一个项目还好。

如果以后有:

Server A
Server B
Server C

Project A
Project B
Project C
Project D
Project E

这些操作就会开始重复。

于是我产生了一个新的想法:

给自己开发一个轻量级 DevOps + Server Management 平台。

平台不需要一开始就在所有服务器安装 Agent。

第一阶段完全可以:

DevOps Platform
│
│ SSH
┌────────────┼────────────┐
▼ ▼ ▼
Server A Server B Server C
│ │ │
Docker Docker Docker

只需要服务器支持:

Linux
SSH
Docker

平台就可以完成:

服务器资源监控
Docker Container 管理
Docker Image 管理
Docker Compose 部署
项目管理
环境变量管理
域名管理
Nginx 配置
HTTPS
日志查看
版本发布
版本回滚

最终希望达到:

创建应用

应用:
Writing Assistant

镜像:
writing-assistant-server:v1.0.0

服务器:
Production-01

端口:
8000

域名:
api.example.com

HTTPS:
开启

环境变量:
********

[部署]

点击部署以后:

检查服务器 Success
检查 Docker Success
Pull Image Success
Run Migration Success
Start Container Success
Health Check Success
Create Nginx Config Success
Reload Nginx Success
Configure HTTPS Success

Deployment Successful

从:

“我会使用 Docker 部署项目。”

进一步走向:

“我把自己的整个部署流程平台化。”

这也是我接下来准备继续实践的方向。


二十三、总结

这次实际部署以后,整个思路已经非常清晰。

开发阶段:

Source Code
↓
Docker Build
↓
Local Test

发布阶段:

Docker Image
↓
Registry
↓
Version Management

生产阶段:

docker compose pull
↓
docker compose up -d
↓
Health Check

流量入口:

Domain
↓
HTTPS
↓
Nginx
↓
127.0.0.1:8000
↓
Docker
↓
FastAPI

内部服务:

FastAPI
├── mysql:3306
└── redis:6379

Celery
└── redis:6379

数据:

Container
↓
Docker Volume
↓
Persistent Data
↓
Regular Backup

真正理解这些关系以后,再看 Docker 就不会只是:

docker run
docker ps
docker logs

几个零散命令了。

它真正解决的是软件工程中一个非常实际的问题:

如何把一个在开发环境运行正常的复杂应用,以可重复、可管理、可版本化的方式交付到生产环境。

而当部署项目越来越多以后,下一个值得解决的问题,也就自然从“怎么部署”变成了:

怎么把部署本身自动化、标准化、平台化。

这也是我下一阶段准备继续折腾的方向。

赞(0)
未经允许不得转载:171主机测评 » Docker 实战部署:从本地镜像到云服务器,一篇走通 FastAPI + Celery + MySQL + Redis + Nginx
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址