Local to Live: The Complete Deployment Chain for an AI Project
An AI project from local to production: what steps are involved? The full process explained
1. The beginning of the story: it works locally, now what?
A while ago, I finished an AI project — XueMianTong AI. The frontend, backend, database, and vector store all ran fine locally: open localhost, and everything works. But how do you get this project online so others can access it?
I walked the whole path and found it breaks down into three parts:
- Infrastructure: server, domain, and certificate — build the "foundation" first.
- Engineering changes: containerization, gateway, data persistence — make the project "qualified to go live".
- Deployment execution: actually push the code up and get the services running.
This article is the overview of the entire deployment series. It gives you the full map first, and later chapters dive into hands-on work for each part.
2. Step one: get a "computer that never shuts down" — a cloud server
A local machine needs to be powered off, can lose network access, and has no public address, so it clearly cannot serve external traffic.
So the first thing is to buy a cloud server: essentially a Linux machine in the cloud that stays on 24/7, used to store code and run programs.
A small reminder: after you buy it, go to the security group and only allow the necessary ports (usually 80, 443, and 22 for login). Do not open database, Redis, or other ports to the public internet; we'll handle those separately later.
3. Step two: give the server a memorable name — domain and DNS
Once you buy a server, you get a public IP, such as 47.xx.xx.xx. You could access it with the IP, but a long string of numbers is hard to remember and looks unprofessional.
That is what domains are for: they make it easy for users to remember and access.
But there's a fundamental principle to understand: internet communication works on IP addresses, not domain names. When a browser receives a domain, it first goes through DNS resolution, translating the domain into the server's public IP before it can actually find your machine.
So the complete action here is: buy a domain → add a DNS resolution record pointing to the server's public IP.
A small reminder: if the server is in mainland China, the domain needs ICP filing (beian) before it can serve websites publicly. Get started early because filing takes time.
4. Step three: lock the access — HTTPS certificate
Now users can reach your site by domain, but the browser address bar may still show "Not secure". The reason is that data is transmitted in plaintext between the browser and the server.
An HTTPS certificate encrypts that transfer, protecting data.
There's a common ordering trap here:
⚠️ The certificate is bound to the domain, so you must own the domain first before you can apply for the corresponding certificate.
At this point, the "foundation" is set: server + domain + certificate. Next it's time to "operate" on the project itself.
5. Step four: pack the services into "shipping containers" — Docker + Docker Compose
Let's count the services XueMianTong runs:
- FastAPI: backend business service
- MySQL: relational database
- Redis: cache
- MinIO: file storage
- Qdrant: vector database
If you installed, configured, and launched each one manually on the server, environment differences alone would eat a whole day: version conflicts, missing dependencies, "but it worked on my machine".
The solution is containerization: use Docker to wrap each service into an independent container, one service per container, then write a docker-compose.yml with Docker Compose to uniformly orchestrate how all containers start.
Once orchestrated, a single command brings up the whole stack:
docker compose up -d
Besides saving effort, containerization has an important security benefit: Docker has a built-in private network. Containers talk to each other on that network, and their services are not directly exposed to the public. For example, the outside world can't reach your MySQL, giving the database a layer of natural protection.
A minimal orchestration skeleton looks roughly like this (just for illustration; real config will be expanded in later chapters):
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 exposes ports to the outside; all other services stay hidden on the internal network.
6. Step five: put a "reception desk" in front of all requests — Nginx as the unified gateway
All services are on the internal network, so how do user requests get in?
The answer: every request first goes through Nginx. It is like a company's front desk, receiving all visitors and routing them as needed:
| User request | How Nginx handles it |
|---|---|
| Frontend static files (pages, JS, CSS) | Returns them directly, fast |
| Backend API calls | Reverse proxy, forwards to the backend service on the internal network |
| File requests | Also forwarded and handled |
At the same time, Nginx reads the server's certificates and handles HTTPS encryption and decryption. So encryption, routing, and forwarding — these common operations — are all solved at a single entry point, which is easier to manage and improves security.
The core configuration looks like this:
server {
listen 80;
server_name your-domain.com;
return 301 https://$host$request_uri; # HTTP auto-redirect to 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;
# frontend static files
location / {
root /usr/share/nginx/html;
try_files $uri $uri/ /index.html;
}
# backend API reverse proxy
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;
}
}
Here, backend is the service name in Compose — since the containers are on the same internal network, they can address each other directly by service name.
7. Step six: containers can be deleted, but data must not be lost — data persistence
Containerization brings a new problem: containers are "disposable". Once a container is deleted and recreated, the data inside it disappears too — user data in MySQL, vectors in Qdrant would all be gone.
The solution is named volumes. Look back at the Compose skeleton above; every stateful service mounts a volume, such as mysql_data, redis_data.
This way, data is actually stored on the Linux server's disk, not inside the container. Even if the container restarts or is deleted and recreated, the original data remains intact.
8. The actual deployment: push the code to the cloud
When all the "equipment" is ready, it's finally time to deploy. The whole execution flow is as follows:
Configure
.gitignore: ignore files that don't need to be uploaded, such as node_modules, local caches, and keys.Build the frontend: run the build command to produce the dist directory, which Nginx will serve.
Prepare the environment variable file: put database passwords, keys, and other configuration into
.env(do not commit it to Git).Upload code to the cloud server: either pull with Git or transfer with scp / rsync.
Build and start the containers: run a Compose command on the server to pull images and install dependencies automatically:
docker compose up -d --build
Run database migrations: after the containers start, MySQL is still empty. Use Alembic migration commands to automatically create the required tables inside the MySQL container:
docker compose exec backend alembic upgrade head
At this point, visit your domain to see the online XueMianTong.
9. Putting it together: the complete journey of one request
When a user types a domain in the browser and initiates an action, the full chain is:
user → enters domain
↓ DNS resolves to the server's public IP
Nginx (unified entry, HTTPS decryption)
↓
┌─────┴──────────────┐
static files API requests
returned directly forwarded to the FastAPI backend
↓
MySQL / Redis / MinIO / Qdrant
(each does its own task)
↓
the result returns to the browser along the same path
- Static files: Nginx returns them directly;
- API requests: forwarded to the backend, which connects to MySQL, Redis, MinIO, and Qdrant, each doing its part;
- Response: the result travels back to the user along the same path.
10. Summary: the three key points of deployment
Looking back, the whole chain condenses into three things:
- Get the infrastructure in place first: buy the server, buy and resolve the domain, apply for the HTTPS certificate (domain first, then certificate);
- Containerize and orchestrate with Docker + Docker Compose: write multiple services into one config file, start all services with a single command, dependencies download automatically — no manual installation;
- Unified gateway with Nginx + named volume persistence: one entry point handles HTTPS, routing, and proxying for security; named volumes guarantee data is not lost.