· fly-io / heroku / deployment

Cách chuyển ứng dụng Heroku sang Fly.io theo từng bước

Fly.io chạy ứng dụng Heroku với chi phí ~$4/tháng. Hướng dẫn từng bước với lệnh CLI đã kiểm chứng cho Postgres, Redis, file storage và scheduled tasks.

Bởi

1.383 từ · 7 phút đọc

Chuyển sang Fly.io. Một ứng dụng Heroku tiêu chuẩn — web dyno cộng Postgres — chạy trên Fly.io với chi phí khoảng $4/tháng, trong khi Heroku tối thiểu $10/tháng. Với hầu hết ứng dụng, quá trình migration mất chưa đến một giờ. Một điều cần biết trước: Fly Managed Postgres có giá $38/tháng và đắt hơn Heroku với ứng dụng nhỏ. Hãy dùng self-managed (fly postgres) thay thế.

Dành cho ai

Những ai có ứng dụng Heroku đang chạy và muốn chuyển mà không cần viết lại. Ứng dụng Node.js, Python và Ruby migrate được trực tiếp — fly launch tự nhận diện runtime và tạo Dockerfile từ Procfile của bạn. Nếu bạn chưa quyết định nền tảng nào phù hợp, Railway vs Render so sánh hai lựa chọn khác không cần Dockerfile.

Chúng tôi đã thử gì

Fly.io CLI (flyctl v0.4.x), Heroku CLI v9.x. Ứng dụng Node.js với Postgres, Redis, và file upload. Các lệnh được kiểm tra theo tài liệu chính thức của Fly.io tính đến ngày 2026-05-30.


Bước 1: Cài flyctl và đăng nhập

# macOS
brew install flyctl

# Linux
curl -L https://fly.io/install.sh | sh

fly auth login

Lỗi thường gặp: Trên Linux không có curl, dùng wget -qO- https://fly.io/install.sh | sh.


Bước 2: Chạy fly launch trong thư mục ứng dụng

cd your-app
fly launch

flyctl đọc Procfile của bạn, nhận diện runtime, và tạo ra Dockerfile cùng fly.toml. Dockerfile sẽ được tạo mới ngay cả khi trước đó bạn chưa có — đây là hành vi bình thường.

Lỗi thường gặp: Nếu flyctl không nhận diện được runtime, nó sẽ hỏi builder nào bạn muốn dùng. Chọn cái tương ứng với Heroku buildpack của bạn (Node.js, Python, Ruby, v.v.).


Bước 3: Tạo database Postgres

Self-managed Postgres chạy khoảng $2/tháng với instance shared nhỏ nhất và là lựa chọn đúng cho hầu hết ứng dụng nhỏ. Fly Managed Postgres (fly mpg) bắt đầu từ $38/tháng — bỏ qua trừ khi bạn cần automated failover và hỗ trợ kỹ thuật.

fly postgres create --name myapp-db

Lưu lại connection string được in ra — bạn sẽ cần nó ở Bước 5.

Lỗi thường gặp: Nếu có prompt chọn region, hãy chọn cùng region với Bước 2. Kết nối database giữa các region khác nhau sẽ thêm 40–200ms latency.


Bước 4: Gắn database vào ứng dụng

fly postgres attach myapp-db --app myapp

Lệnh này tự động đặt DATABASE_URL vào secrets của ứng dụng. Bạn không cần copy connection string đi đâu cả.


Bước 5: Import dữ liệu Postgres từ Heroku

fly postgres import xử lý dump và restore trong một lệnh duy nhất — cách nhanh nhất.

# Lấy connection string của Heroku
heroku pg:credentials:url -a your-heroku-app DATABASE

# Import vào Fly Postgres
fly postgres import <heroku-connection-string> --app myapp-db

Lệnh này sẽ block cho đến khi hoàn tất và in ra tiến trình. Với database trên 1 GB, thêm --quiet và để nó chạy.

Lỗi thường gặp: Nếu import thất bại với lỗi permission, user database Heroku của bạn có thể thiếu quyền pg_read_all_data. Sửa trước:

heroku pg:psql -a your-heroku-app -c "GRANT pg_read_all_data TO <heroku-user>"

Fallback thủ công (nếu fly postgres import không khả dụng):

pg_dump -Fc --no-acl --no-owner \
  -h <heroku-host> -U <heroku-user> <heroku-db> > heroku.dump

fly postgres connect -a myapp-db
# bên trong psql:
pg_restore --verbose --clean --no-acl --no-owner \
  -h localhost -d myapp heroku.dump

Bước 6: Chuyển environment variables

Bỏ qua DATABASE_URLREDIS_URL — bạn đã xử lý Postgres rồi, còn Redis sẽ làm ở bước sau. Import mọi thứ còn lại trong một lệnh:

heroku config -s -a your-heroku-app \
  | grep -v DATABASE_URL \
  | grep -v REDIS_URL \
  | fly secrets import

Lỗi thường gặp: heroku config -s đặt quote khác nhau tùy shell. Nếu có lỗi import, đặt từng biến một:

fly secrets set MY_SECRET="value" ANOTHER_KEY="value"

Bước 7: Cài Redis (nếu ứng dụng dùng Redis)

Fly Redis được cung cấp bởi Upstash. Tạo bằng lệnh:

fly redis create --name myapp-redis

Lưu ý: Fly Redis không thể import dữ liệu Redis cũ trực tiếp. Với hầu hết ứng dụng, bỏ queued jobs và session data là ổn — khởi động từ đầu và để queue tự điền lại. Nếu bạn cần khôi phục các key cụ thể, export chúng từ Heroku Redis bằng redis-cli --rdb rồi restore qua redis-cli --pipe vào Fly Redis URL.


Bước 8: Cài file storage (nếu ứng dụng ghi file)

Fly có filesystem tạm thời (ephemeral). File ghi lên ổ đĩa của một machine không tồn tại sau khi restart và không xuất hiện trên máy khác. Nếu ứng dụng của bạn xử lý upload hoặc tạo file, hãy cài object storage trước khi deploy:

fly storage create --name myapp-storage

Lệnh này tạo một Tigris bucket. Thêm BUCKET_NAME và access keys được in ra vào secrets của ứng dụng, rồi cập nhật app để dùng S3-compatible API. Cloudflare R2 là lựa chọn thay thế không tính phí egress.

Lỗi thường gặp: Bỏ qua bước này khiến file xuất hiện trong session của machine rồi biến mất ở lần deploy tiếp theo. Heroku cũng có giới hạn ephemeral filesystem tương tự, nhưng một số ứng dụng đã dùng attached storage layers — những thứ này sẽ không tự chuyển sang.


Bước 9: Xử lý scheduled tasks (nếu dùng Scheduler dyno)

Heroku’s Scheduler dyno không có tương đương trực tiếp trên Fly. Có hai cách:

Cách A — clock process trong fly.toml:

[processes]
  app = "node server.js"
  clock = "node scripts/daily-job.js"

[[services]]
  processes = ["app"]

Cách B — fly machine run với schedule:

fly machine run --app myapp \
  --schedule daily \
  --entrypoint "node scripts/daily-job.js" \
  .

Bước 10: Deploy

fly deploy

flyctl build Docker image, push lên, khởi động các machine, và chạy health check. Thành công trông như thế này: v1 deployed successfully.

Lỗi thường gặp: Lỗi health check sẽ hiện trong output deploy. Kiểm tra fly logs để xem lỗi startup. Nguyên nhân phổ biến nhất là ứng dụng bind port cứng thay vì dùng process.env.PORT. Fly mặc định kỳ vọng ứng dụng ở 0.0.0.0:8080 (có thể cấu hình trong fly.toml).


Bước 11: Tránh cold starts (tùy chọn)

Mặc định, Fly scale về zero khi không có request trong vài phút. Cold start có thể mất vài giây với runtime JVM hoặc .NET. Để giữ ít nhất một machine luôn chạy:

# fly.toml
[http_service]
  min_machines_running = 1

Với shared-cpu-1x, một machine luôn bật thêm khoảng $1.94/tháng.


Bước 12: Dọn dẹp Heroku

Sau khi xác nhận traffic đã route đúng sang Fly:

heroku apps:destroy your-heroku-app --confirm your-heroku-app

Lưu ý: Xóa ứng dụng Fly sau này KHÔNG xóa các resource Postgres, Redis, hay Tigris đã gắn kèm. Xóa chúng riêng lẻ không thì bạn vẫn bị tính phí:

fly postgres destroy myapp-db
fly redis destroy myapp-redis
fly storage destroy myapp-storage

So sánh chi phí

Cấu hìnhChi phí hàng tháng
Heroku Eco dyno + Essential-0 Postgres~$10 tối thiểu
Fly shared-cpu-1x + self-managed Postgres~$4
Fly shared-cpu-1x + Managed Postgres Basic~$40

Fly Managed Postgres ở mức $38/tháng đắt gấp bốn lần Heroku với ứng dụng nhỏ. Self-managed mới là đích đến đúng khi migration.


Kết luận

Fly.io cắt giảm chi phí Heroku khoảng 60% với ứng dụng nhỏ. CLI xử lý việc nhận diện runtime, tạo Dockerfile, và import dữ liệu mà không cần bạn viết config từ đầu. Dùng self-managed Postgres, bỏ qua Managed. Thêm Tigris nếu ứng dụng có file upload.

Nếu bạn muốn so sánh các nền tảng trước khi quyết định, Railway vs Render so sánh hai lựa chọn khác không cần Dockerfile và có mô hình tính phí đơn giản hơn. Nếu đã chọn Fly nhưng muốn đánh giá chi tiết so với đối thủ gần nhất, Fly.io vs Railway phân tích kỹ về chi phí, độ tin cậy và sự phù hợp cho từng quy mô team.