GitHub Actions và GitLab CI cho backend, web và ứng dụng di động
So sánh GitHub Actions và GitLab CI cho monorepo: cấu hình runner, quản lý secret, caching và mẫu pipeline thực tế cho backend, web và ứng dụng di động.

Những vấn đề thường gặp trong CI cho nhiều app
Khi một repo build backend, một web app và các app di động, CI không còn là "chỉ chạy test" nữa. Nó trở thành bộ điều phối lưu lượng cho các toolchain khác nhau, thời gian build khác nhau và quy tắc phát hành khác nhau.
Vấn đề phổ biến nhất thì đơn giản: một thay đổi nhỏ kích hoạt quá nhiều công việc. Sửa tài liệu mà khơi mào signing iOS, hay chỉnh backend mà buộc phải rebuild toàn bộ web, và đột nhiên mọi merge đều cảm thấy chậm và rủi ro.
Trong các thiết lập nhiều app, vài vấn đề xuất hiện sớm:
- Runner drift: phiên bản SDK khác nhau trên các máy, nên build chạy khác nhau giữa CI và local.
- Secrets tràn lan: API key, chứng chỉ signing và thông tin cửa hàng bị nhân bản khắp nơi giữa các job và môi trường.
- Nhầm lẫn cache: key cache sai tạo build stale, nhưng không có cache thì mọi thứ chậm đau lòng.
- Quy tắc release lẫn lộn: backend muốn deploy thường xuyên, còn mobile bị gated và cần kiểm tra thêm.
- Độ đọc được của pipeline: cấu hình phình thành một bức tường job không ai muốn động vào.
Vì vậy sự lựa chọn giữa GitHub Actions và GitLab CI quan trọng hơn trong monorepo so với dự án đơn app. Bạn cần cách rõ ràng để tách công việc theo đường dẫn, chia sẻ artifact an toàn, và ngăn các job song song đè lên nhau.
So sánh thực tế gói gọn vào bốn thứ: cấu hình và scale runner, lưu trữ và phạm vi secret, caching và artifacts, và mức độ dễ biểu đạt "chỉ build những gì thay đổi" mà không biến pipeline thành bãi rules mong manh.
Đây là về độ tin cậy và khả năng duy trì hàng ngày, không phải nền tảng nào có nhiều tích hợp hơn hay giao diện đẹp hơn. Nó cũng không thay thế quyết định về công cụ build của bạn (Gradle, Xcode, Docker, v.v.). Nó giúp bạn chọn CI làm sao để cấu trúc rõ ràng dễ giữ sạch.
Cách GitHub Actions và GitLab CI được tổ chức
Khác biệt lớn nhất là mỗi nền tảng tổ chức pipeline và tái dùng khác nhau, và điều đó bắt đầu có ý nghĩa khi backend, web và mobile cùng chia sẻ repo.
GitHub Actions lưu automation trong file YAML dưới .github/workflows/. Workflows kích hoạt theo sự kiện như push, pull request, schedule, hoặc chạy thủ công. GitLab CI tập trung quanh .gitlab-ci.yml ở gốc repo, với các file include tùy chọn, và pipeline thường chạy trên push, merge request, schedule và các job thủ công.
GitLab xây dựng quanh stages. Bạn định nghĩa các stage (build, test, deploy), rồi gán job vào các stage đó chạy theo thứ tự. GitHub Actions xây dựng quanh workflows chứa jobs. Jobs chạy song song theo mặc định, và bạn thêm dependencies khi cần chờ.
Để chạy cùng logic trên nhiều target, matrix builds của GitHub là lựa chọn tự nhiên (iOS vs Android, nhiều phiên bản Node). GitLab có thể fan-out tương tự dùng parallel jobs và variables, nhưng bạn thường phải nối nhiều mảnh với tay hơn.
Tái dùng cũng khác. Trong GitHub, đội thường dựa vào reusable workflows và composite actions. Trong GitLab, tái dùng thường đến từ include, template chia sẻ, và YAML anchors/extends.
Approval và protected environments cũng khác nhau. GitHub hay dùng protected environments với reviewer bắt buộc và environment secrets để deploy production dừng lại chờ phê duyệt. GitLab thường kết hợp protected branches/tags, protected environments và manual jobs để chỉ vai trò nhất định mới chạy deploy được.
Cấu hình runner và thực thi job
Cấu hình runner là nơi hai nền tảng bắt đầu cảm thấy khác nhau trong sử dụng hàng ngày. Cả hai đều chạy job trên hosted runners (không quản lý máy) hoặc self-hosted runners (bạn sở hữu máy, cập nhật và bảo mật). Hosted runners dễ bắt đầu; self-hosted thường cần khi cần nhanh hơn, tool đặc thù, hoặc truy cập mạng nội bộ.
Phân chia thực tế ở nhiều đội là Linux runner cho backend và web, và macOS runner chỉ khi cần build iOS. Android chạy được trên Linux, nhưng nặng, nên kích thước runner và dung lượng đĩa quan trọng.
Hosted vs self-hosted: bạn quản lý gì
Hosted runner phù hợp khi bạn muốn setup dự đoán mà không bảo trì. Self-hosted hợp lý khi cần Java/Xcode phiên bản cụ thể, cache nhanh hơn, hoặc truy cập nội bộ.
Nếu dùng self-hosted, định nghĩa vai trò runner sớm. Hầu hết repo hoạt tốt với một tập nhỏ: runner Linux chung cho backend/web, runner Linux mạnh hơn cho Android, runner macOS cho packaging và signing iOS, và runner riêng cho deploy với quyền hạn thắt chặt.
Chọn runner phù hợp cho từng job
Cả hai hệ thống cho phép target runner (labels trong GitHub, tags trong GitLab). Đặt tên theo workload, như linux-docker, android, hoặc macos-xcode15.
Isolation là nơi nhiều flaky build đến từ. File thừa, cache chia sẻ bị hỏng, hoặc tools cài "thủ công" trên self-hosted có thể tạo lỗi ngẫu nhiên. Workspace sạch, khóa phiên bản tool, và dọn runner theo lịch thường trả lại lợi ích nhanh.
Dung lượng và quyền là những điểm đau lặp đi lặp lại, đặc biệt với chi phí và tính khả dụng macOS. Mặc định tốt là: build runners chỉ build, deploy runners chỉ deploy, và credential production chỉ sống trong tập job nhỏ nhất có thể.
Secrets và biến môi trường
Secrets là nơi pipeline đa-app trở nên rủi ro. Nguyên tắc cơ bản giống nhau (lưu secrets trên nền tảng, inject khi chạy), nhưng phạm vi cảm giác khác.
GitHub Actions thường scope secret ở mức repo và tổ chức, với một lớp Environment thêm vào. Lớp Environment hữu ích khi production cần cổng tay và tập giá trị khác so với staging.
GitLab CI dùng CI/CD variables ở mức project, group, hoặc instance. Nó cũng hỗ trợ environment-scoped variables, cùng protections như "protected" (chỉ dành cho protected branches/tags) và "masked" (ẩn trong logs). Những điều khiển này hữu ích khi một monorepo phục vụ nhiều đội.
Failure mode chính là lộ ngẫu nhiên: output debug, command lỗi in ra biến, hoặc artifact vô tình chứa file config. Hãy coi logs và artifacts là có thể được chia sẻ theo mặc định.
Trong pipeline backend + web + mobile, secrets thường gồm credential cloud, URL database và API third-party, signing material (chứng chỉ/provisioning iOS, Android keystore và mật khẩu), token registry (npm, Maven, CocoaPods), và token tự động (email/SMS, chat bots).
Với nhiều môi trường (dev, staging, prod), giữ tên nhất quán và thay giá trị theo scope môi trường thay vì copy job. Điều đó giúp xoay vòng và kiểm soát truy cập dễ quản lý.
Một vài quy tắc ngăn hầu hết sự cố:
- Ưu tiên credential ngắn hạn (như OIDC tới nhà cung cấp cloud khi có) hơn key dài hạn.
- Dùng least privilege: tách identity deploy cho backend, web và mobile.
- Mask secrets và tránh in biến môi trường, kể cả khi thất bại.
- Hạn chế secret production cho protected branches/tags và reviewer bắt buộc.
- Không bao giờ lưu secret trong build artifacts, kể cả tạm thời.
Ví dụ tác động cao đơn giản: mobile job chỉ nhận signing secrets khi release có tag, trong khi backend deploy job dùng deploy token giới hạn khi merge vào main. Thay đổi đó giảm đáng kể blast radius nếu một job cấu hình sai.
Caching và artifacts để tăng tốc build
Hầu hết pipeline chậm vì một lý do nhàm chán: tải và build cùng thứ hết lần này sang lần khác. Caching tránh làm lại. Artifacts giải quyết bài toán khác: giữ chính xác đầu ra từ một lần chạy cụ thể.
Cần cache gì tùy vào gì bạn build. Backend hưởng lợi từ cache dependency và compiler (ví dụ cache build của Go). Web hưởng lợi từ cache package manager và tool build. Mobile thường cần Gradle cộng với cache Android SDK trên Linux, và CocoaPods hoặc Swift Package Manager trên macOS. Cẩn thận với cache đầu ra iOS quá mức (như DerivedData) trừ khi hiểu trade-off.
Cả hai nền tảng theo pattern: restore cache lúc bắt đầu job, save cache cập nhật ở cuối job. Khác nhau hàng ngày là mức độ kiểm soát. GitLab làm rõ hành vi cache và artifact trong một file, bao gồm expiration. GitHub Actions thường dựa vào các action riêng cho caching, linh hoạt nhưng dễ cấu hình sai.
Cache key quan trọng hơn trong monorepo. Key tốt thay đổi khi input thay đổi và ổn định khi không. Lockfile (go.sum, pnpm-lock.yaml, yarn.lock, v.v.) nên lái key. Cũng hữu ích khi bao gồm hash của folder app cụ thể bạn build thay vì toàn repo, và giữ cache riêng cho từng app để một thay đổi không invalidate mọi thứ.
Dùng artifacts cho deliverable bạn muốn giữ: release bundles, APK/IPA, báo cáo test, file coverage và metadata build. Cache là tăng tốc best-effort; artifact là hồ sơ.
Nếu build vẫn chậm, tìm cache quá to, key đổi mỗi lần (timestamp hoặc commit SHA đầy đủ), và output cached không tái sử dụng được giữa các runner.
Phù hợp với monorepo: nhiều pipeline mà không lo hỗn loạn
Monorepo trở nên lộn xộn khi mọi push kích hoạt backend test, web build và mobile signing, dù bạn chỉ sửa README. Mẫu sạch là: phát hiện gì thay đổi, chỉ chạy job có liên quan.
Trong GitHub Actions, điều này thường là nhiều workflow riêng cho mỗi app với path filters để mỗi workflow chỉ chạy khi file trong vùng của nó thay đổi. Trong GitLab CI, thường là một file pipeline dùng rules:changes (hoặc child pipelines) để tạo hoặc bỏ nhóm job dựa trên path.
Packages chia sẻ là nơi niềm tin vỡ. Nếu packages/auth thay đổi, cả backend và web có thể cần rebuild dù folder của chúng không đổi. Xử lý thư mục chia sẻ như trigger cho nhiều pipeline và giữ ranh giới phụ thuộc rõ ràng.
Bản đồ trigger đơn giản giữ bất ngờ ở mức thấp:
- Backend jobs chạy khi thay đổi
backend/**hoặcpackages/**. - Web jobs chạy khi thay đổi
web/**hoặcpackages/**. - Mobile jobs chạy khi thay đổi
mobile/**hoặcpackages/**. - Thay đổi chỉ docs chạy kiểm tra nhanh (formatting, spellcheck).
Song song hóa những gì an toàn (unit tests, linting, web build). Tuần tự hóa những gì cần điều khiển (deploy, app store release). Cả needs của GitLab và dependencies job của GitHub giúp bạn chạy kiểm tra nhanh sớm và dừng phần còn lại khi chúng fail.
Giữ signing mobile tách biệt khỏi CI hàng ngày. Đặt signing keys trong environment riêng với approval thủ công, và chạy signing chỉ trên tagged releases hoặc branch protected. Pull request bình thường vẫn có thể build unsigned app để kiểm tra mà không lộ secret nhạy cảm.
Từng bước: pipeline sạch cho backend, web và mobile
Pipeline đa-app rõ ràng bắt đầu với cách đặt tên dễ hiểu. Chọn một pattern và bám theo để mọi người nhìn log biết được gì đã chạy.
Một sơ đồ dễ đọc:
- Pipelines:
pr-checks,main-build,release - Environments:
dev,staging,prod - Artifacts:
backend-api,web-bundle,mobile-debug,mobile-release
Từ đó, giữ job nhỏ và chỉ promote những gì đã qua kiểm tra trước:
-
PR checks (mọi pull request): chạy test nhanh và lint chỉ cho app thay đổi. Với backend, build artifact deployable (container image hoặc server bundle) và lưu để bước sau không phải build lại.
-
Web build (PR + main): build web thành static bundle. Trên PR, giữ output như artifact (hoặc deploy tới preview nếu có). Trên main, tạo bundle versioned phù hợp cho
devhoặcstaging. -
Mobile debug builds (chỉ PR): build APK/IPA debug. Không sign release. Mục tiêu là phản hồi nhanh và file tester có thể cài.
-
Release builds (chỉ tags): khi push tag như
v1.4.0, chạy full backend và web build cùng signed mobile release builds. Tạo output ready-for-store và giữ release notes kèm artifact. -
Approval thủ công: đặt approval giữa
stagingvàprod, không đặt trước kiểm tra cơ bản. Dev có thể trigger build, nhưng chỉ vai trò được phê duyệt mới deploy production và truy cập secret production.
Sai lầm phổ biến tốn thời gian
Đội thường mất tuần vì thói quen workflow tạo builds flaky âm thầm.
Một cạm bẫy là phụ thuộc quá mức vào shared runners. Khi nhiều project tranh cùng pool, bạn gặp timeout ngẫu nhiên, job chậm và mobile build fail chỉ vào giờ cao điểm. Nếu backend, web và mobile đều quan trọng, cô lập job nặng trên runner riêng (hoặc ít nhất queue riêng) và đặt giới hạn tài nguyên rõ ràng.
Secrets là nguồn khác ngốn thời gian. Mobile signing keys và chứng chỉ dễ xử lý sai. Sai lầm phổ biến là lưu chúng quá rộng (được mọi branch và job truy cập) hoặc lộ qua logs verbose. Giữ signing material giới hạn cho protected branches/tags và tránh mọi bước in giá trị secret (kể cả base64).
Caching phản tác dụng khi cache thư mục khổng lồ hoặc lẫn lộn cache và artifacts. Cache chỉ inputs ổn định. Giữ outputs cần thiết làm artifacts.
Cuối cùng, trong monorepo, kích hoạt mọi pipeline cho mọi thay đổi đốt thời gian vô ích. Nếu ai đó sửa README mà bạn rebuild iOS, Android, backend và web, người ta sẽ mất niềm tin vào CI.
Checklist nhanh hữu ích:
- Dùng rule dựa đường dẫn để chỉ chạy app bị ảnh hưởng.
- Tách job test khỏi job deploy.
- Giữ signing keys hạn chế cho workflow release.
- Cache nhỏ, inputs ổn định, không cache toàn bộ build folder.
- Lên kế hoạch capacity runner rõ ràng cho mobile builds nặng.
Kiểm tra nhanh trước khi quyết định nền tảng
Trước khi chọn, làm vài kiểm tra phản ánh cách bạn thực sự làm việc. Chúng cứu bạn khỏi chọn công cụ chạy ổn cho một app nhưng đau đầu khi thêm mobile, nhiều môi trường và release.
Tập trung vào:
- Kế hoạch runner: hosted, self-hosted, hay trộn. Mobile thường đẩy đội về trộn vì iOS cần macOS.
- Kế hoạch secret: secret ở đâu, ai đọc được, và cách xoay vòng. Production chặt hơn staging.
- Kế hoạch cache: cache gì, lưu ở đâu, và key được tạo ra thế nào. Nếu key đổi mỗi commit, bạn trả tiền mà không có tốc độ.
- Kế hoạch monorepo: path filters và cách chia sẻ bước chung (lint, tests) mà không copy-paste.
- Kế hoạch release: tags, approvals và tách môi trường. Rõ ai có quyền promote production và cần bằng chứng gì.
Thử áp lực các câu trả lời đó với một kịch bản nhỏ. Trong monorepo có backend Go, web Vue và hai mobile app: thay đổi chỉ docs nên gần như không làm gì; thay đổi backend chạy test backend và build API artifact; thay đổi UI mobile chỉ build Android và iOS.
Nếu bạn không thể mô tả flow đó trên một trang (triggers, caches, secrets, approvals), chạy pilot một tuần trên cả hai nền tảng dùng cùng repo. Chọn nền tảng cảm thấy tẻ nhạt và dự đoán được.
Ví dụ: flow build và release thực tế cho monorepo
Hình dung một repo có ba thư mục: backend/ (Go), web/ (Vue) và mobile/ (iOS và Android).
Hàng ngày, bạn muốn phản hồi nhanh. Khi release, bạn muốn full builds, signing và publish.
Phân chia thực tế:
- Feature branches: chạy lint + unit tests cho phần thay đổi, build backend và web, và tùy chọn build Android debug. Bỏ iOS trừ khi thực sự cần.
- Release tags: chạy mọi thứ, tạo artifact versioned, sign mobile app và đẩy images/binaries lên nơi lưu release.
Quyết định runner thay đổi khi có mobile. Go và Vue chạy tốt trên Linux ở hầu hết nơi. iOS cần macOS runner, điều này có thể ảnh hưởng quyết định nhiều hơn bất cứ điều gì khác. Nếu đội muốn full control máy build, GitLab CI với self-hosted có thể dễ quản lý như một fleet. Nếu bạn thích ít ops và thiết lập nhanh, GitHub hosted runner tiện lợi, nhưng số phút macOS và tính khả dụng cần được tính.
Caching là nơi tiết kiệm thời gian thực sự, nhưng cache tốt nhất khác nhau theo app. Với Go, cache module downloads và build cache. Với Vue, cache package manager store và rebuild khi lockfiles đổi. Với mobile, cache Gradle và Android SDK trên Linux; cache CocoaPods hoặc Swift Package Manager trên macOS, và chấp nhận cache lớn hơn và invalidate nhiều hơn.
Quy tắc quyết định bền vững: nếu mã đã host trên một nền tảng, bắt đầu từ đó. Chỉ chuyển khi runner (đặc biệt macOS), quyền, hoặc compliance bắt buộc phải đổi.
Bước tiếp theo: chọn, chuẩn hóa và tự động an toàn
Chọn công cụ phù hợp với nơi mã và người của bạn đang ở. Hầu hết thời gian, khác biệt lộ ra ở ma sát hàng ngày: review, quyền, và tốc độ ai đó chẩn đoán build hỏng.
Bắt đầu đơn giản: một pipeline cho mỗi app (backend, web, mobile). Khi ổn định, rút các bước chung vào template tái dùng để giảm copy-paste mà không làm mờ ownership.
Ghi rõ phạm vi secret như bạn ghi ai có chìa khóa văn phòng. Secret production không nên đọc được bởi mọi branch. Đặt nhắc xoay vòng (hàng quý tốt hơn không bao giờ), và thống nhất cách revoke khẩn cấp hoạt động.
Nếu bạn build bằng công cụ no-code sinh source thực tế, coi generation/export là bước CI quan trọng. Ví dụ, AppMaster (appmaster.io) sinh backend Go, web Vue3 và mobile Kotlin/SwiftUI, nên pipeline có thể regen code khi thay đổi, rồi chỉ build target bị ảnh hưởng.
Khi team có flow tin tưởng, biến nó thành mặc định cho repo mới và giữ nó tẻ: trigger rõ ràng, runner dự đoán, secret chặt, và release chỉ chạy khi bạn thực sự muốn.
Câu hỏi thường gặp
Ưu tiên nền tảng nơi mã nguồn và đội ngũ của bạn đang làm việc, rồi chỉ chuyển nếu runner (đặc biệt macOS), quyền truy cập, hoặc yêu cầu tuân thủ bắt buộc bạn phải đổi. Chi phí hàng ngày thường nằm ở tính khả dụng của runner, phạm vi secret, và mức độ dễ biểu đạt "chỉ build những gì thay đổi" mà không tạo ra các rule mong manh.
GitHub Actions thường cảm thấy đơn giản hơn để thiết lập nhanh và phù hợp với matrix builds, với workflow tách trong nhiều file YAML. GitLab CI thường tập trung hơn và theo mô hình stage-driven, điều này có thể dễ lý giải hơn khi pipeline lớn và bạn muốn một chỗ để kiểm soát cache, artifact và thứ tự job.
Xem macOS như một tài nguyên khan hiếm và chỉ dùng khi cần packaging hoặc signing iOS. Một baseline phổ biến là runner Linux cho backend và web, một runner Linux mạnh hơn cho Android, và một runner macOS dành riêng cho job iOS, cùng với runner deploy riêng có quyền hạn chặt hơn.
Runner drift xảy ra khi cùng một job chạy khác nhau do SDK và tool khác nhau trên từng máy. Khắc phục bằng cách khóa phiên bản tool, tránh cài đặt thủ công trên runner tự quản, dùng workspace sạch, và định kỳ dọn hoặc rebuild image runner để không tích tụ các khác biệt vô hình theo thời gian.
Chỉ cấp secrets cho tập job nhỏ nhất cần chúng, và giữ secret production phía sau protected branches/tags cùng các approval. Với mobile, mặc định an toàn là chỉ inject signing material cho các release tagged, còn pull request chỉ build debug unsigned để test.
Dùng cache để tăng tốc công việc lặp lại, và artifacts để lưu lại kết quả chính xác của một lần chạy. Cache là nỗ lực tối ưu hóa và có thể được thay thế theo thời gian, còn artifact là deliverable bạn muốn giữ như bản ghi (bundle, test report, APK/IPA release...).
Dựa key cache vào inputs ổn định như lockfile, và scope nó vào phần repo bạn build để thay đổi không liên quan không làm mất cache. Tránh key đổi mỗi lần (ví dụ timestamp hoặc commit SHA đầy đủ), và giữ cache riêng cho từng app để backend, web và mobile không tranh nhau cùng một cache.
Dùng rule dựa trên đường dẫn để docs hoặc thư mục không liên quan không khởi động các job tốn kém, và coi các package chia sẻ là trigger rõ ràng cho những app phụ thuộc. Nếu thư mục chia sẻ thay đổi thì việc rebuild nhiều target là hợp lý, nhưng hãy làm cho mapping đó có chủ ý để pipeline giữ được tính dự đoán.
Giữ signing keys và thông tin cửa hàng ngoài các lần chạy CI hàng ngày bằng cách gate chúng phía sau tag, protected branches và approval. Với pull request, build biến debug không signing release để vẫn có phản hồi nhanh mà không phơi bày credential rủi ro cao.
Có, nhưng biến việc generate thành bước quan trọng với inputs và outputs rõ ràng để dễ cache và chạy lại có dự đoán. Nếu bạn dùng công cụ như AppMaster (appmaster.io) sinh code thực tế, cách sạch là regen khi có thay đổi liên quan, rồi chỉ build những target bị ảnh hưởng dựa trên thay đổi sau khi regen.


