一、故事的开头:本地跑通了,然后呢?
前段时间,我做完了一个 AI 项目—— 学面通AI。前端、后端、数据库、向量库都已经在本地跑得好好的:打开 localhost,功能一切正常。但是要怎样才把这个项目上线?让其他人也能在线上访问
我把这条路走了一遍,发现可以拆成三块:
- 基础设施:服务器、域名、证书,先把“地基”打好;
- 工程化改造:容器化、网关、数据持久化,让项目具备“上线的资格”;
- 部署执行:真正把代码送上去,让服务跑起来。
这篇文章是整个部署系列的总览,先带你把全局地图看清楚,后面的章节再逐个动手实操。
二、第一步:先有一台“永不关机的电脑”——云服务器
本地电脑要关机、要断网、没有公网地址,显然不能拿来对外提供服务。
所以第一件事是购买一台云服务器:它本质上就是一台放在云端、24 小时开机的 Linux 电脑,用来存放代码、运行程序。
小提醒:买完之后记得到安全组里只放行必要端口(通常是 80、443,以及用于登录的 22)。数据库、Redis 等端口不要对公网开放,后面我们会专门处理。
三、第二步:给服务器起个好记的名字——域名与 DNS
服务器买好后,会得到一个公网 IP,比如 47.xx.xx.xx。理论上用 IP 就能访问,但一长串数字既不好记,也不专业。
这就是域名存在的意义:方便用户记忆和访问。
不过这里有个底层原理要搞清楚:互联网通信靠的是 IP 地址,不是域名。浏览器拿到域名后,要先经过 DNS 解析,把域名翻译成服务器的公网 IP,才能真正找到你的机器。
所以这一步的完整动作是:购买域名 → 添加 DNS 解析记录,指向服务器公网 IP。
小提醒:如果服务器在中国大陆,域名需要先完成 ICP 备案才能正常对外提供网站服务,建议尽早办理,因为备案需要时间。
四、第三步:给访问加把锁——HTTPS 证书
现在用户已经能通过域名访问网站了,但浏览器地址栏可能还显示“不安全”。原因是数据在浏览器和服务器之间是明文传输的。
HTTPS 证书的作用,就是让这段传输过程被加密,保障数据安全。
这里有一个容易踩的顺序坑:
⚠️ 证书是和域名绑定的,所以必须先拥有域名,才能申请对应的证书。
到这里,“地基”就打好了:服务器 + 域名 + 证书。接下来,轮到项目本身“动手术”了。
五、第四步:把一堆服务装进“集装箱”——Docker + Docker Compose
我们来数一数学面通在跑的服务:
- FastAPI:后端业务服务
- MySQL:关系型数据
- Redis:缓存
- MinIO:文件存储
- Qdrant:向量数据库
如果在服务器上一个个手动安装、配置、启动,光是环境差异就够折腾一整天:版本冲突、依赖缺失、“我本地明明是好的”……
解决办法是容器化:用 Docker 把每个服务封装成独立的容器,一个服务一个容器;再用 Docker Compose 写一份 docker-compose.yml,把所有容器的启动方式统一编排好。
编排完成后,只需要一条命令,就能把整套服务一起拉起来:
docker compose up -d
除了省事,容器化还有个很重要的安全收益:Docker 内置了私有内网,容器之间在内网中互相通信,里面的服务不会直接暴露在公网。比如外网根本访问不到你的 MySQL,这就等于给数据库加了一层天然的保护。
一个精简版的编排骨架大概长这样(仅作示意,实际配置会在后续章节展开):
services:
nginx:
image: nginx:stable
ports:
- "80:80"
- "443:443"
volumes:
- ./frontend/dist:/usr/share/nginx/html:ro
- ./nginx/conf.d:/etc/nginx/conf.d:ro
- ./certs:/etc/nginx/certs:ro
depends_on:
- backend
backend: # FastAPI
build: ./backend
env_file: .env
depends_on:
- mysql
- redis
- minio
- qdrant
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
volumes:
- mysql_data:/var/lib/mysql
redis:
image: redis:7
volumes:
- redis_data:/data
minio:
image: minio/minio
command: server /data --console-address ":9001"
volumes:
- minio_data:/data
qdrant:
image: qdrant/qdrant
volumes:
- qdrant_data:/qdrant/storage
volumes:
mysql_data:
redis_data:
minio_data:
qdrant_data:
注意:只有 nginx 对外暴露了端口,其余服务都藏在内网里。
六、第五步:给所有请求装一个“前台”——Nginx 统一网关
服务都在内网里了,用户的请求怎么进来?
答案是:所有请求都先经过 Nginx。它就像公司的前台,统一接待所有访客,再按需求分流:
同时,Nginx 还会读取服务器上的证书,完成 HTTPS 的加解密。也就是说,加密、分流、转发这些“共同操作”,都在统一入口一次性解决,既方便管理,也提升了安全性。
核心配置的思路大致如下:
server {
listen 80;
server_name your-domain.com;
return 301 https://$host$request_uri; # HTTP 自动跳转 HTTPS
}
server {
listen 443 ssl;
server_name your-domain.com;
ssl_certificate /etc/nginx/certs/your-domain.com.pem;
ssl_certificate_key /etc/nginx/certs/your-domain.com.key;
# 前端静态资源
location / {
root /usr/share/nginx/html;
try_files $uri $uri/ /index.html;
}
# 后端 API 反向代理
location /api/ {
proxy_pass http://backend:8000;
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;
}
}
这里的 backend 就是 Compose 里的服务名——容器在同一个内网中,可以直接用服务名互相访问。
七、第六步:容器删了,数据可不能丢——数据持久化
容器化带来一个新问题:容器是“可丢弃”的。一旦容器被删除重建,里面的数据也会跟着消失,MySQL 里的用户数据、Qdrant 里的向量,岂不是全没了?
解决方案是命名数据卷(named volume)。回头看上面的 Compose 骨架,你会发现每个有状态的服务都挂了一个卷,比如 mysql_data、redis_data。
这样一来,数据实际保存在 Linux 服务器的磁盘上,而不是容器内部。即使容器重启、甚至被删除重建,原有数据依然完好。
八、真正的部署:把代码送上云端
“装备”都备齐了,终于可以动手部署了。整个执行流程如下:
- 配置 .gitignore:把 node_modules、本地缓存、密钥等不需要上传的文件忽略掉;
- 前端打包:运行打包命令,生成 dist 目录,交给 Nginx 托管;
- 准备环境变量文件:把数据库密码、各类密钥等配置到 .env(注意不要提交到 Git);
- 上传代码到云服务器:可以通过 Git 拉取,或用 scp / rsync 上传;
- 构建并启动容器:在服务器上执行 Compose 命令,自动拉取镜像、安装依赖:
docker compose up -d --build
- 数据库迁移:容器启动后,MySQL 里还是一片空白。通过 Alembic 迁移命令,自动在 MySQL 容器中创建所需的数据表:
docker compose exec backend alembic upgrade head
到这一步,访问你的域名,就能看到线上的学面通了。
九、串起来看:一个请求的完整旅程
当用户在浏览器输入域名并发起操作,整个链路是这样的:
用户 → 输入域名访问
↓ DNS 解析为服务器公网 IP
Nginx(统一入口,HTTPS 解密)
↓
┌─────┴──────────────┐
静态资源 API 请求
直接返回 转发给 FastAPI 后端
↓
MySQL / Redis / MinIO / Qdrant
(分工完成不同任务)
↓
结果原路返回浏览器
- 静态资源:Nginx 直接返回;
- API 请求:转发给后端,后端连接 MySQL、Redis、MinIO、Qdrant,各司其职完成任务;
- 响应:结果沿原路返回给用户。
十、总结:部署的三个重点
回头看,整条链路可以浓缩成三件事:
- 基础设施先到位:购买服务器、域名并解析、申请 HTTPS 证书(先有域名,再申请证书);
- Docker + Docker Compose 容器化编排:多个服务统一写进一份配置文件,一条命令启动全部服务,依赖自动下载,不用手动逐个安装;
- Nginx 统一网关 + 数据卷持久化:统一入口做 HTTPS、分流与代理,保障安全;命名数据卷保证数据不丢失。
本文由 AI图鉴自动抓取并翻译,版权归原作者所有;如需查看原文请点下方链接。
i. The beginning of the story: local runs, then?
A while ago, I finished an AI project -- learning to read AI. The front-end, back-end, database, vector bank are already running well locally: open localhost, all functional. But how do we get this project online?
I went over this road and I found three pieces:
- Infrastructure: servers, domain names, certificates, first setting the foundations;
- Engineering modifications: containerization, gateways, data durability, making the project “ready to go online”;
- Deployment execution: really send the code up and let the service run.
This article is an overview of the entire series of deployments, which will show you the global map first, followed by the next chapter, one by one.
First step: a “computer never shut down” — cloud servers
Local computers need to shut down, cut off the net, and have no public address, and obviously cannot be used for external services.
So the first thing is to buy a cloud server: it's basically a Linux computer that's on the cloud, 24 hours a day, to store codes, run programs.
Small alarm: Remember to leave only the necessary ports (usually 80, 443, and 22 for login) in the security team after the purchase. Databases, Redis, etc., are not open to the Internet, and we'll handle them exclusively later.
Step 2: Give the server a good name - domain name and DNS
When the server is bought, you get a public network IP, like 47.xx.xx.xx. Theoretically, an IP can be accessed, but a long list of numbers is neither easy to remember nor professional.
This is the meaning of the domain name: user memory and access.
But here's the bottom line: Internet communications are based on IP addresses, not domain names. When the browser gets the domain name, you have to go through the DNS and translate the domain name into the server's public network IP before you really find your machine.
So the complete action of this step is to purchase domain names and add DNS parsing records to the server public network IP.
Small reminder: If a server is in mainland China, the domain name needs to be completed before the ICP file can normally provide a website service to the outside world. It is recommended that this be done as soon as possible, as filing takes time.
Step 3: Lock Access - HTTPS Certificate
Users are now able to access the website through domain names, but the browser address column may also show “unsafe”. The reason is that data are explicitly transmitted between browsers and servers.
The HTTPS certificate serves to encrypt the transfer and secure the data.
There's an easy sequence hole:
The certificate is bound to the domain name, so the domain name must be in order to apply for the corresponding certificate.
Here, the Base is finished: Server + Domain + Certificate. Then it's the project itself.
Step 4: Loading a collection of services in a “container” - Docker + Docker Company
Let's count a mathematically running service:
- FastAPI: back-end business services
- MySQL: Relationship data
- Redis: cache
- MINIO: File storage
- Qdrant: vector data Library
If a server is installed, configured and activated manually, it's enough that the environment is different: conflict of versions, lack of reliance, "I'm a good place"...
The solution is containerization: each service is sealed in a separate container with Docker, one container, and a copy of the docker-compose.yml is written by Docker Company, so that all containers are organized in a uniform manner.
Once the organization is complete, only one order will be required to pull up the whole service:
docker compose up -d
Apart from the economy, containerization has an important safety gain: Docker has a private intranet, communicates between containers in the inner net, and the services within it are not directly exposed to the public network. For example, you have no access to MySQL on the Extranet, which adds a natural layer of protection to the database.
A condensed version of the skeletal skeleton is roughly this long (just indicative, the actual configuration will be extended in a subsequent section):
services:
nginx:
image: nginx:stable
ports:
- "80:80"
- "443:443"
volumes:
- ./frontend/dist:/usr/share/nginx/html:ro
- ./nginx/conf.d:/etc/nginx/conf.d:ro
- ./certs:/etc/nginx/certs:ro
depends_on:
- backend
backend: # FastAPI
build: ./backend
env_file: .env
depends_on:
- mysql
- redis
- minio
- qdrant
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
volumes:
- mysql_data:/var/lib/mysql
redis:
image: redis:7
volumes:
- redis_data:/data
minio:
image: minio/minio
command: server /data --console-address ":9001"
volumes:
- minio_data:/data
qdrant:
image: qdrant/qdrant
volumes:
- qdrant_data:/qdrant/storage
volumes:
mysql_data:
redis_data:
minio_data:
qdrant_data:
Note: Only nginx has exposed the port to the outside world, and the rest of the services are stored on the Intranet.
Step 5: Installing a “top” - Nginx unified gateway for all requests
The service's on the inside. How do you get in here?
The answer is: all requests go through Nginx first. It's like the front desk of the company, where all visitors are integrated and diverted according to demand:
At the same time, Nginx will also read certificates on the server and complete the decryption of HTTPS. In other words, encryption, diversion and the forwarding of these “joint operations” are all one-off solutions at the unified entrance, which are easy to manage and enhance security.
The rationale for the core configuration is as follows:
server {
listen 80;
server_name your-domain.com;
# HTTP autojumping HTTPS
}
server {
listen 443 ssl;
server_name your-domain.com;
ssl_certificate /etc/nginx/certs/your-domain.com.pem;
ssl_certificate_key /etc/nginx/certs/your-domain.com.key;
# Front-end static resources
location / {
root /usr/share/nginx/html;
try_files $uri $uri/ /index.html;
}
# Backend API Reverse Agent
location /api/ {
proxy_pass http://backend:8000;
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;
}
}
The backend here is the name of the service in the Compose -- the container is in the same intranet and can be accessed directly by the service name.
Step 6: The container has been deleted and the data cannot be lost — data sustainability
Containerization poses a new problem: packagings are “discardable”. Once the container has been removed and rebuilt, the data will disappear.
The solution is a named data volume. Looking back at the copose skeleton on it, you'll find every service in a state of service with a roll, like mysql data, redis data.
As a result, the data is actually stored on the disk of the Linux server rather than inside the container. Even if the containers were restarted or even deleted, the original data would remain intact.
VIII. Genuine deployment: sending the code to the cloud
The “equipment” is ready and can finally be deployed. The overall implementation process is as follows:
- Configure.gitignore: ignore files that need not be uploaded, such as node modules, local cache, keys;
- Front-end packing: run packing commands, generate dist directories and hand them over to Nginx hosting;
- Preparation of environmental variables files: configure database passwords, keys, etc. to.env (note not to submit to Git);
- Upload code to cloud server: can be retrieved by Git or uploaded using scp / rsync;
- Build and start the container: execute the Compose command on the server, automatically pull mirrors, install dependency:
docker compose up -d --build
- Database migration: MySQL remains blank after the container is activated. Automatically create the required data tables in MySQL packagings through the Alembic migration command:
docker compose exec backend alembic upgrade head
At this point, access to your domain name will be visible online.
IX. STANDING TO SEE: A COMPLETE THROUGH REQUEST
When users enter domain names and initiate operations in browsers, the entire link is this:
User → Enter Domain Access
DNS parsing as server public network IP
Nginx (Unified entrance, HTTPS decryption)
↓
┌─────┴──────────────┐
Static Resource API Request
Go straight back to FastAPI backend
↓
MySQL / Redis / MinIO / Qdrant
(Segment of tasks)
↓
Turns out it's back to the browser.
- Static resources: Nginx returns directly;
- API request: forward to the backend, which connects MySQL, Redis, MinIO, Qdrant and performs their duties;
- Response: results returned to users along the same path.
Summary: three deployment priorities
Looking back, the whole link can be condensed into three things:
- Infrastructure is in place: purchase of servers, domain names and deciphering, application for HTTPS certificates (domain names before certification);
- Docker + Docker Compose containerization: integration of multiple services into a configuration file, one order to start all services, reliance on automatic downloads, and no manual installation;
- Nginx Harmonized Gateway + Endurance of Data Volumes: Unified access for HTTPS, diversions and agents, secure; naming data files to ensure that data are not lost.
Auto-fetched and translated by AI Atlas. All rights belong to the original author.