Danh sách bài viết

Bài 36: Deploy lên Render — đơn giản nhất cho starter

Render là PaaS cho phép deploy app trực tiếp từ git repo mà không cần quản lý server. Bài này trình bày toàn bộ quy trình: từ chuẩn bị repo, tạo Web Service trên dashboard, cấu hình environment variables, dùng render.yaml để khai báo cơ sở hạ tầng dưới dạng code, đến các pitfall thường gặp khi deploy FastAPI AI app.

27/05/2026
3 lượt xem
1

Mục tiêu bài học

Sau bài này bạn sẽ:

  • ✅ Hiểu Render là gì, phù hợp với use case nào và giới hạn ở đâu
  • ✅ Deploy được FastAPI app lên Render bằng Docker hoặc buildpacks
  • ✅ Cấu hình environment variables và secret an toàn
  • ✅ Khai báo toàn bộ infrastructure (Web Service + DB + Redis) bằng render.yaml
  • ✅ Biết plan nào phù hợp với từng giai đoạn dự án
  • ✅ Nhận biết và tránh các pitfall phổ biến khi deploy AI app
2

Render là gì và khi nào dùng

Render là PaaS (Platform-as-a-Service): bạn push code lên git, Render tự build image và chạy container. Bạn không cần cấu hình VM, firewall, load balancer hay certificate — Render lo phần đó.

So với AWS/GCP, Render đánh đổi sự linh hoạt để đổi lấy tốc độ triển khai. Đây là lựa chọn hợp lý khi:

  • Cần đưa POC hoặc demo lên public URL nhanh.
  • App đơn giản: FastAPI + Postgres + Redis, traffic không quá lớn.
  • Team nhỏ, chưa có người chuyên về infra.
  • Không muốn học AWS console hay GCP IAM.

Khi nào KHÔNG dùng Render

  • Workload GPU liên tục: Render có GPU instance (T4/A10G/A100, ra mid-2024) nhưng số lượng hạn chế và giá không rẻ hơn AWS/GCP đáng kể. Nếu cần train hoặc inference nặng 24/7, xem xét các lựa chọn chuyên biệt.
  • Traffic cao: trên ~10k req/s, Render có thể không đủ linh hoạt để scale ngang như Kubernetes.
  • Data sovereignty / on-prem: Render chạy trên cloud (AWS và GCP infrastructure). Nếu luật yêu cầu data ở máy chủ trong nước, đây không phải lựa chọn đúng.
  • Fine-grained network control: VPC peering, custom security group, private subnet — những thứ này Render không hỗ trợ đầy đủ.
3

Service types trên Render

Render cung cấp nhiều loại service; mỗi loại phục vụ một mục đích khác nhau:

Service type Dùng cho Có public URL?
Web Service API server, web app — nhận HTTP request
Background Worker Task worker, queue consumer — không nhận HTTP Không
Static Site Frontend build (React, Vue, Next.js static export)
Cron Job Lệnh chạy theo lịch (cron expression) Không
Private Service Service nội bộ, chỉ accessible từ service khác trong cùng account Không
PostgreSQL Managed Postgres Không (chỉ connection string)
Redis Managed Redis Không (chỉ connection string)

Trong bài này tập trung vào Web Service — loại phổ biến nhất khi deploy FastAPI. Background Worker và Cron Job dùng cấu hình tương tự, chỉ khác ở chỗ không có HTTP listener.

4

Chuẩn bị repo

Trước khi tạo service trên Render, repo cần đáp ứng một số điều kiện.

Cách 1: Dùng Dockerfile (khuyến nghị cho AI app)

Khi repo có Dockerfile, Render sẽ tự phát hiện và chạy docker build. Đây là cách được khuyến nghị vì bạn kiểm soát hoàn toàn environment — Python version, system packages (ví dụ libgomp cho PyTorch), và CUDA nếu cần.

# Dockerfile (ví dụ minimal FastAPI inference)
FROM python:3.11-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

# Render truyền $PORT tại runtime — không hard-code 8000
CMD ["sh", "-c", "uvicorn main:app --host 0.0.0.0 --port $PORT"]

Điểm quan trọng: app phải bind 0.0.0.0:$PORT. Render set env var PORT tại runtime với một giá trị port ngẫu nhiên. Hard-code 8000 sẽ khiến health check fail và service bị restart liên tục.

Cách 2: Buildpacks (Python native, không cần Dockerfile)

Nếu repo có requirements.txt, Render tự phát hiện Python và chạy buildpack:

  • Build Command: pip install -r requirements.txt
  • Start Command: uvicorn main:app --host 0.0.0.0 --port $PORT

Buildpack phù hợp cho app nhẹ. Khi app cần system package ngoài Python (libsndfile cho audio, libGL cho OpenCV, hay bất kỳ C extension nào), dùng Dockerfile để cài thêm tường minh.

Cấu trúc repo tối thiểu

my-ai-app/
├── Dockerfile
├── requirements.txt
├── main.py          # FastAPI app
├── .env.example     # tên biến cần set, giá trị trống
└── render.yaml      # tùy chọn, xem bước 8

File .env thật (chứa secret) phải có trong .gitignore, không commit lên repo.

5

Tạo Web Service từ dashboard

  1. Đăng ký tài khoản tại render.com (login bằng GitHub hoặc GitLab).
  2. Dashboard → nhấn New + → chọn Web Service.
  3. Chọn Connect a repository → authorize GitHub/GitLab nếu chưa. Chọn repo cần deploy.
  4. Render hiển thị form cấu hình:
Trường Ghi chú
Name Tên service, xuất hiện trong subdomain: name.onrender.com
Region Oregon (US West), Frankfurt, Singapore, Ohio... Chọn Singapore cho user ở VN để giảm latency
Branch Branch git để theo dõi, thường là main
Runtime Render auto-detect Docker nếu có Dockerfile. Hoặc chọn Python, Node, Ruby, etc.
Build Command Bỏ trống nếu dùng Docker (Render tự docker build)
Start Command Bỏ trống nếu dùng Docker (Render dùng CMD từ Dockerfile)
Instance Type Chọn plan phù hợp (xem bước 9)

Nhấn Create Web Service. Render bắt đầu clone repo, build image và deploy. Log build hiện trực tiếp trên dashboard.

URL sau deploy

Khi deploy thành công, Render cấp URL dạng https://<name>.onrender.com. HTTPS tự động, không cần cấu hình thêm.

6

Build command và Start command

Khi dùng Docker

Render tự chạy docker build .docker run với CMD trong Dockerfile. Bỏ trống Build Command và Start Command trên dashboard.

Khi dùng buildpacks (không có Dockerfile)

Build Command:  pip install -r requirements.txt
Start Command:  uvicorn main:app --host 0.0.0.0 --port $PORT

Nếu dùng gunicorn thay vì uvicorn:

Start Command:  gunicorn main:app -w 2 -k uvicorn.workers.UvicornWorker --bind 0.0.0.0:$PORT

Pre-deploy command

Render hỗ trợ trường Pre-Deploy Command — chạy sau khi build xong nhưng trước khi start. Dùng để chạy database migration:

Pre-Deploy Command:  python -m alembic upgrade head

Nếu pre-deploy command fail, Render không triển khai build mới — giữ nguyên version cũ đang chạy. Đây là cơ chế an toàn để tránh deploy app với schema DB chưa migrate.

7

Environment variables

Trên dashboard của service, chọn tab Environment.

Thêm variable đơn lẻ

Nhấn Add Environment Variable, nhập key và value. Ví dụ:

OPENAI_API_KEY   = sk-...
MODEL_NAME       = gpt-4o-mini
LOG_LEVEL        = info

Secret values được mã hóa sau khi lưu — Render không hiển thị lại giá trị. Bạn chỉ có thể ghi đè hoặc xóa.

Environment Group — chia sẻ biến giữa nhiều service

Nếu nhiều service dùng chung một tập biến (ví dụ API key cho cả Web Service và Background Worker), tạo một Environment Group:

  1. Dashboard → Environment GroupsNew Environment Group.
  2. Đặt tên (ví dụ ai-api-secrets), thêm các key/value.
  3. Vào từng service → Environment → Link Environment Group.

Khi cập nhật giá trị trong group, các service link đến group sẽ nhận giá trị mới ở lần deploy tiếp theo.

DATABASE_URL tự inject

Khi tạo Postgres database trên Render và link với service, Render tự inject DATABASE_URL vào env của service. Không cần copy connection string thủ công.

8

render.yaml — Infrastructure as Code

render.yaml là file khai báo toàn bộ infrastructure của một project — Web Service, database, Redis, biến môi trường — dưới dạng code. Commit file này vào repo rồi dùng tính năng Blueprint để deploy toàn bộ stack với một click.

# render.yaml
services:
  - type: web
    name: ai-api
    runtime: docker
    repo: https://github.com/you/ai-app
    branch: main
    region: singapore
    plan: starter
    healthCheckPath: /health
    envVars:
      - key: OPENAI_API_KEY
        sync: false          # set thủ công trên dashboard, không sync từ file
      - key: MODEL_NAME
        value: gpt-4o-mini
      - key: DATABASE_URL
        fromDatabase:
          name: my-pg
          property: connectionString
      - key: REDIS_URL
        fromService:
          name: my-redis
          type: redis
          property: connectionString

  - type: worker
    name: ai-worker
    runtime: docker
    repo: https://github.com/you/ai-app
    branch: main
    region: singapore
    plan: starter
    startCommand: python worker.py
    envVars:
      - key: OPENAI_API_KEY
        sync: false
      - key: DATABASE_URL
        fromDatabase:
          name: my-pg
          property: connectionString

  - type: redis
    name: my-redis
    plan: free
    region: singapore
    ipAllowList: []          # chỉ cho phép kết nối nội bộ (internal only)

databases:
  - name: my-pg
    plan: free
    region: singapore
    databaseName: appdb
    user: appuser

Giải thích các trường quan trọng

  • sync: false: biến có giá trị nhạy cảm (API key) — không lưu trong file, set thủ công trên dashboard sau khi Blueprint tạo service.
  • fromDatabase: Render tự resolve connection string của database tương ứng và inject vào env. Không cần copy tay.
  • healthCheckPath: Render gọi endpoint này sau deploy để xác nhận app healthy. App phải trả về HTTP 200. Thiếu trường này → Render không biết app đã sẵn sàng chưa.
  • ipAllowList: []: Redis chỉ cho phép kết nối từ service nội bộ trong cùng account, không expose ra internet.

Dùng Blueprint

Sau khi commit render.yaml vào repo:

  1. Render Dashboard → New +Blueprint.
  2. Connect repo → Render đọc render.yaml và hiển thị preview toàn bộ service sẽ tạo.
  3. Nhấn Apply → Render tạo tất cả service, database, Redis cùng lúc.

Blueprint giúp onboard thành viên mới hoặc tái tạo environment dễ dàng — tất cả cấu hình đã được version control.

9

Plan và pricing

Các con số dưới đây phản ánh thông tin tại thời điểm viết bài (mid-2025). Kiểm tra render.com/pricing để có số liệu mới nhất trước khi ra quyết định.

Plan Giá/tháng RAM CPU Ghi chú
Free $0 512 MB 0.1 vCPU Sleep sau 15 phút idle, quota 750h/month
Starter ~$7 512 MB 0.5 vCPU Không sleep, chạy 24/7
Standard ~$25 2 GB 1 vCPU Đủ cho inference SLM nhẹ (DistilBERT, TinyLlama)
Pro ~$85 4 GB 2 vCPU Persistent disk, đủ RAM cho nhiều model nhỏ
Pro Plus ~$175 8 GB 4 vCPU Model lớn hơn, multi-worker
GPU (T4) Pay-as-you-go 16 GB VRAM 4 vCPU + T4 Inference GPU, ra mid-2024

Free tier — điểm cần lưu ý

  • Sleep sau 15 phút idle: request đầu tiên sau khi service ngủ cần 10–30 giây để wake up. Không phù hợp cho production có user thật.
  • 750h/month quota: đủ cho một service chạy liên tục (30 ngày × 24h = 720h). Nếu có nhiều service Free, quota chia sẻ.
  • Postgres Free: tự động xóa sau 90 ngày nếu không upgrade. Backup data trước khi hết hạn.

Chọn plan theo giai đoạn

  • POC / internal demo: Free tier, chấp nhận sleep.
  • Demo public, user thật: Starter ($7) — không sleep, đủ cho API gọi OpenAI/Anthropic.
  • App với model nhỏ on-device (DistilBERT, Sentence Transformers): Standard ($25) — 2GB RAM.
  • App cần lưu model file (Whisper base, etc.): Pro trở lên — cần persistent disk.
10

Database và Redis

PostgreSQL

Tạo từ dashboard (New + → PostgreSQL) hoặc khai báo trong render.yaml. Render tự quản lý backup, upgrade, failover.

  • Free plan: 1GB storage, expire sau 90 ngày.
  • Starter plan (~$7/month): 1GB storage, không expire.
  • Standard plan trở lên: nhiều RAM hơn cho query phức tạp, connection pooling.

Connection string có dạng:

postgresql://user:[email protected]/dbname

Render cung cấp cả InternalExternal connection string:

  • Internal URL: dùng trong code chạy trên Render — kết nối nhanh hơn, không qua internet.
  • External URL: dùng từ máy local (ví dụ pgAdmin, psql để debug).

Redis

  • Free plan: 25MB, không persist (data mất khi restart). Chỉ dùng cho cache tạm.
  • Starter plan (~$10/month): 250MB, persist.

Sau khi tạo Redis service và link với Web Service, Render inject REDIS_URL vào env. Trong code Python:

import os
import redis

r = redis.from_url(os.environ["REDIS_URL"])
r.set("key", "value", ex=3600)  # TTL 1h
11

Persistent disk

Mặc định, filesystem của Render container là ephemeral: data ghi vào sẽ mất khi service redeploy hoặc restart. Nếu cần lưu file qua các lần deploy (model weights, user uploads, cache), cần Persistent Disk.

Persistent Disk có sẵn từ Pro plan trở lên. Cấu hình trong render.yaml:

services:
  - type: web
    name: ai-api
    runtime: docker
    plan: pro
    disk:
      name: model-storage
      mountPath: /data
      sizeGB: 10

Trong code, đọc/ghi file dưới /data như filesystem thông thường:

import os
from pathlib import Path

MODEL_DIR = Path(os.getenv("MODEL_DIR", "/data/models"))
MODEL_DIR.mkdir(parents=True, exist_ok=True)

# Download model lần đầu, lần sau đọc từ disk
model_path = MODEL_DIR / "whisper-base.pt"
if not model_path.exists():
    download_model(model_path)

Persistent Disk gắn với một service cụ thể — nếu scale lên nhiều instance, mỗi instance có disk riêng, không chia sẻ. Nếu cần shared storage giữa nhiều instance, phải dùng object storage ngoài (S3, GCS).

12

Logs, metrics và monitoring

Logs

Dashboard → service → tab Logs. Log hiển thị real-time, có thể filter theo level hoặc tìm kiếm chuỗi. Render giữ log trong 7 ngày (Free/Starter) hoặc lâu hơn tùy plan.

App ghi ra stdout/stderr như bình thường — Render tự capture:

import logging
import sys

logging.basicConfig(
    stream=sys.stdout,
    level=logging.INFO,
    format="%(asctime)s %(levelname)s %(name)s %(message)s",
)
logger = logging.getLogger(__name__)

logger.info("Model loaded: %s", model_name)

Metrics

Dashboard → tab Metrics: CPU usage, RAM usage, request count, response time. Biểu đồ theo thời gian thực, đủ để phát hiện memory leak hoặc spike CPU trong inference.

Alerts

Render hỗ trợ alert qua email khi service crash hoặc health check fail liên tục. Cấu hình trong Settings → Notifications.

Tích hợp ngoài

Render hỗ trợ log drain: forward log đến Datadog, New Relic, Papertrail, Logtail qua HTTP endpoint. Cấu hình trong Dashboard → Log Streams.

13

Auto-deploy và manual deploy

Auto-deploy (mặc định bật)

Mỗi khi có commit mới trên branch đang track, Render tự trigger build và deploy. Luồng:

  1. Push code lên GitHub/GitLab.
  2. Render nhận webhook, clone repo, build image.
  3. Nếu build thành công và health check pass → swap sang version mới.
  4. Nếu build fail → version cũ tiếp tục chạy.

Tắt auto-deploy

Vào Settings → Auto-Deploy → tắt. Dùng khi bạn muốn kiểm soát thủ công thời điểm deploy (ví dụ chờ QA approve).

Manual deploy từ dashboard

Dashboard → service → nhấn Manual Deploy → chọn commit hoặc branch. Nếu build gần đây thất bại, nút Retry xuất hiện để chạy lại build đó.

Deploy via API

Render có REST API cho tự động hóa từ CI/CD pipeline:

curl -X POST \
  "https://api.render.com/v1/services/${SERVICE_ID}/deploys" \
  -H "Authorization: Bearer ${RENDER_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{"clearCache": "do_not_clear"}'
14

Custom domain và HTTPS

Render cấp HTTPS tự động cho tất cả service (kể cả Free tier) qua subdomain .onrender.com. Để dùng domain riêng:

  1. Dashboard → service → tab SettingsCustom Domains.
  2. Nhập domain (ví dụ api.yourdomain.com).
  3. Render hiển thị DNS record cần thêm: thường là CNAME trỏ về your-service.onrender.com.
  4. Vào DNS provider (Cloudflare, Namecheap, GoDaddy, ...) thêm record.
  5. Sau khi DNS propagate (thường vài phút đến 1 giờ), Render tự cấp certificate Let's Encrypt và bật HTTPS.

Render tự gia hạn certificate — bạn không cần làm gì thêm.

15

Ví dụ end-to-end — FastAPI inference app

Dưới đây là ví dụ triển khai FastAPI app gọi OpenAI để inference, dùng Dockerfile, deploy lên Render.

Cấu trúc repo

ai-inference-api/
├── Dockerfile
├── requirements.txt
├── main.py
└── render.yaml

main.py

import os
from fastapi import FastAPI
from pydantic import BaseModel
from openai import AsyncOpenAI

app = FastAPI()
client = AsyncOpenAI(api_key=os.environ["OPENAI_API_KEY"])
MODEL = os.getenv("MODEL_NAME", "gpt-4o-mini")


class ChatRequest(BaseModel):
    message: str


class ChatResponse(BaseModel):
    reply: str


@app.get("/health")
async def health():
    return {"status": "ok"}


@app.post("/chat", response_model=ChatResponse)
async def chat(req: ChatRequest):
    completion = await client.chat.completions.create(
        model=MODEL,
        messages=[{"role": "user", "content": req.message}],
    )
    return ChatResponse(reply=completion.choices[0].message.content)

requirements.txt

fastapi==0.115.0
uvicorn[standard]==0.30.6
openai==1.51.0
pydantic==2.9.2

Dockerfile

FROM python:3.11-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY main.py .

CMD ["sh", "-c", "uvicorn main:app --host 0.0.0.0 --port $PORT"]

render.yaml

services:
  - type: web
    name: ai-inference-api
    runtime: docker
    region: singapore
    plan: starter
    healthCheckPath: /health
    envVars:
      - key: OPENAI_API_KEY
        sync: false
      - key: MODEL_NAME
        value: gpt-4o-mini

Quy trình deploy

  1. Push repo lên GitHub.
  2. Render Dashboard → New + → Blueprint → connect repo → Apply.
  3. Render tạo Web Service, đọc render.yaml, bắt đầu build.
  4. Sau khi tạo service: vào tab Environment → set giá trị cho OPENAI_API_KEY.
  5. Trigger manual deploy (hoặc push thêm commit để auto-deploy).
  6. Sau vài phút: service sẵn sàng tại https://ai-inference-api-xxx.onrender.com.

Test nhanh

curl -X POST https://ai-inference-api-xxx.onrender.com/chat \
  -H "Content-Type: application/json" \
  -d '{"message": "Giải thích gradient descent trong 2 câu"}'
16

Common pitfalls

1. Hard-code port thay vì dùng $PORT

# SAI — port 8000 cố định, Render truyền port khác qua $PORT
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

# ĐÚNG — đọc $PORT tại runtime
CMD ["sh", "-c", "uvicorn main:app --host 0.0.0.0 --port $PORT"]

App hard-code port sẽ không nhận được request và Render liên tục restart container.

2. Free tier sleep gây user experience kém

Khi service Free sleep, request đầu tiên phải đợi container warm up (10–30 giây). Nếu app đã có người dùng thật, upgrade lên Starter ($7/month). Workaround tạm: cron job ping service mỗi 10 phút từ bên ngoài — nhưng cách này không đáng tin và vi phạm spirit của Free tier.

3. Build timeout vì cài PyTorch / Transformers

Render có giới hạn build time (mặc định 45 phút). pip install torch transformers có thể tốn 5–15 phút tùy plan. Nếu build thường xuyên timeout:

  • Dùng multi-stage build (bài 34): stage builder cài dependency, stage final chỉ copy artifact.
  • Dùng base image có sẵn PyTorch (pytorch/pytorch:2.4.0-cuda12.1-cudnn8-runtime) để bỏ qua bước cài.
  • Pin version cụ thể trong requirements.txt — tránh pip resolve lâu.

4. Không có health check endpoint

Nếu không khai báo healthCheckPath, Render không thể phân biệt container đang khởi động với container đã crash. Service có thể bị restart liên tục dù app đang chạy bình thường. Thêm endpoint GET /health trả về 200 là đủ.

5. Docker image quá lớn

Render có giới hạn image size (khoảng 8–10GB tùy plan). Image chứa full PyTorch + CUDA + Transformers rất dễ vượt ngưỡng này. Áp dụng multi-stage build (bài 34) để loại bỏ build tool và cache. Dùng --no-cache-dir khi pip install.

6. Free Postgres expire sau 90 ngày

Render tự xóa Postgres Free instance sau 90 ngày không upgrade. Nếu dùng cho dự án có data quan trọng, thiết lập backup định kỳ hoặc upgrade trước khi hết hạn. Render gửi email cảnh báo 30 ngày trước.

7. Region xa user gây latency cao

Render có region Singapore (gần VN nhất), Oregon (US West), Frankfurt, Ohio, Virginia. Với user ở Việt Nam, chọn Singapore. Latency từ Singapore đến HCM khoảng 20–40ms, so với Oregon ~200ms.

8. Filesystem ephemeral — mất file sau redeploy

File ghi vào container filesystem (ngoài persistent disk) sẽ mất khi deploy mới. Không lưu model, user upload, hay state quan trọng vào filesystem mặc định. Dùng persistent disk (Pro plan) hoặc object storage ngoài.

17

Tóm tắt

  • ✅ Render phù hợp cho POC, demo, app nhỏ — không cần quản lý server hay infra phức tạp
  • ✅ App phải bind 0.0.0.0:$PORT — Render set PORT tại runtime, không hard-code
  • ✅ Dùng Dockerfile để kiểm soát environment (Python version, system packages, CUDA)
  • render.yaml khai báo toàn bộ stack (Web Service + DB + Redis) dưới dạng code, deploy 1 click qua Blueprint
  • ✅ Secret qua Environment tab hoặc Environment Group — không hardcode trong YAML
  • ✅ Free tier sleep sau 15 phút idle và Postgres expire sau 90 ngày — upgrade khi cần production-ready
  • ✅ Persistent Disk (Pro plan) cho model weights, file cần survive qua redeploy
  • ✅ Chọn Singapore region cho user ở Việt Nam
  • ✅ Endpoint /health trả 200 là bắt buộc để Render biết service đã sẵn sàng