Tối ưu DevOps nhỏ với Portainer, GitHub Actions, Registry

P P T P Chung

Tối ưu workflow DevOps nhỏ với Portainer, GitHub Actions và Docker Registry

DevOps không nhất thiết phải bắt đầu bằng Kubernetes, service mesh, Helm chart phức tạp, hay một đội platform riêng. Với team nhỏ, startup, side project, SaaS giai đoạn đầu, mục tiêu thường rất thực tế: code push → build image → deploy ổn định → rollback nhanh → ít thao tác tay.

Bộ ba Portainer + GitHub Actions + Docker Registry đáp ứng tốt bài toán này. Chi phí thấp. Dễ hiểu. Dễ vận hành. Đủ mạnh cho nhiều hệ thống nhỏ đến trung bình.

Workflow lý tưởng:

Developer push code → GitHub Actions build Docker image → push vào Registry → Portainer deploy/update container trên server

Kết quả: giảm SSH thủ công, giảm lỗi copy file, chuẩn hóa môi trường, tăng tốc release.


Tổng quan kiến trúc

Một workflow DevOps nhỏ thường gồm:

GitHub: lưu source code. – GitHub Actions: CI/CD pipeline. – Docker Registry: nơi lưu Docker image. – Portainer: UI quản trị Docker/Compose/Stack. – VPS/server: chạy container production/staging.

Luồng cơ bản:

1. Dev push code lên nhánh main. 2. GitHub Actions chạy test/build. 3. Build Docker image. 4. Tag image theo commit SHA/version. 5. Push image lên Docker Registry. 6. Portainer pull image mới. 7. Container được recreate/update. 8. App chạy version mới.

Điểm hay: mỗi phần làm đúng một việc.

– GitHub Actions → automation. – Registry → artifact storage. – Portainer → runtime management. – Docker → packaging/runtime.


Vì sao team nhỏ nên dùng Portainer?

Portainer là giao diện quản trị Docker trực quan. Thay vì phải SSH vào server rồi chạy docker ps, docker logs, docker compose up -d, bạn có UI để:

– Xem container đang chạy. – Kiểm tra logs. – Restart service. – Quản lý volume/network. – Deploy bằng Compose stack. – Rollback bằng đổi image tag. – Quản lý nhiều environment/server.

Với team nhỏ, Portainer giúp giảm phụ thuộc vào một người “biết server”. Người mới có thể xem trạng thái hệ thống nhanh hơn. Việc debug production cũng trực quan hơn.

Tuy vậy, Portainer không thay thế CI/CD. Nó nên là lớp quản lý runtime, không phải nơi build code.


Docker Registry: trái tim của quy trình release

Registry là nơi lưu Docker image sau khi build. Một số lựa chọn phổ biến:

GitHub Container Registry: ghcr.ioDocker HubGitLab Container RegistryHarbor self-hostedAWS ECR / GCP Artifact Registry / Azure ACR

Với GitHub Actions, lựa chọn tiện nhất thường là GitHub Container Registry vì tích hợp sẵn với repo.

Ví dụ image:

ghcr.io/org/my-app:latest
ghcr.io/org/my-app:main
ghcr.io/org/my-app:sha-abc123
ghcr.io/org/my-app:v1.2.0

Không nên chỉ dùng latest cho production. Vì latest khó truy vết. Nên tag theo:

– Commit SHA. – SemVer. – Branch. – Build number.

Khuyến nghị:

ghcr.io/org/my-app:sha-<commit>
ghcr.io/org/my-app:v1.4.2

Production nên deploy tag cố định. Rollback dễ: đổi lại tag cũ.


GitHub Actions: build, test, push image

GitHub Actions giúp tự động hóa pipeline. File thường đặt tại:

.github/workflows/deploy.yml

Pipeline cơ bản:

– Checkout code. – Login registry. – Build Docker image. – Push image. – Gọi webhook hoặc trigger redeploy.

Ví dụ dùng GHCR:

name: Build and Push

on: push: branches: - main

env: IMAGE_NAME: ghcr.io/your-org/your-app

jobs: docker: runs-on: ubuntu-latest

permissions: contents: read packages: write

steps: - name: Checkout uses: actions/checkout@v4

- name: Login GHCR uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }}

- name: Extract metadata id: meta run: | echo "sha_short=${GITHUB_SHA::7}" >> $GITHUB_OUTPUT

- name: Build image run: | docker build -t $IMAGE_NAME:sha-${{ steps.meta.outputs.sha_short }} -t $IMAGE_NAME:main .

- name: Push image run: | docker push $IMAGE_NAME:sha-${{ steps.meta.outputs.sha_short }} docker push $IMAGE_NAME:main

Đây mới là build/push. Chưa deploy. Có 2 hướng deploy phổ biến với Portainer.


Cách deploy với Portainer

Cách 1: Portainer Stack + image tag cố định

Bạn tạo Stack trong Portainer bằng Docker Compose:

services:
  app:
    image: ghcr.io/your-org/your-app:main
    restart: always
    ports:
      - "3000:3000"
    environment:
      NODE_ENV: production
    env_file:
      - stack.env

Sau đó, khi image main mới được push, bạn cần update stack. Có thể thao tác tay trong UI: Pull latest image and redeploy.

Ưu:

– Dễ hiểu. – Không cần automation phức tạp. – Phù hợp giai đoạn đầu.

Nhược:

– Vẫn cần click tay. – Dễ quên deploy. – Không hoàn toàn CI/CD.


Cách 2: Portainer webhook

Portainer hỗ trợ webhook cho service/stack. Khi GitHub Actions build/push xong, gọi webhook để Portainer redeploy.

Luồng:

Push code → build image → push registry → curl Portainer webhook → redeploy

Ví dụ thêm bước cuối:

- name: Trigger Portainer webhook
        run: |
          curl -X POST "${{ secrets.PORTAINER_WEBHOOK_URL }}"

Webhook URL nên lưu trong GitHub Secrets:

PORTAINER_WEBHOOK_URL=https://portainer.example.com/api/webhooks/xxxx

Ưu:

– Tự động deploy. – Setup đơn giản. – Không cần SSH key.

Nhược:

– Cần bảo vệ webhook. – Khó kiểm soát nếu trigger nhầm. – Phụ thuộc image tag strategy.


Thiết kế tag strategy hợp lý

Tag ảnh hưởng trực tiếp tới rollback, audit, debug.

Môi trường staging

Có thể dùng:

your-app:develop
your-app:staging

Mỗi lần merge vào develop → auto deploy staging.

Môi trường production

Nên dùng:

your-app:v1.5.0
your-app:sha-a1b2c3d

Production nên release qua Git tag:

git tag v1.5.0
git push origin v1.5.0

GitHub Actions trigger khi có tag:

on:
  push:
    tags:
      - "v*"

Build image:

ghcr.io/your-org/your-app:v1.5.0

Portainer stack trỏ tới đúng version:

image: ghcr.io/your-org/your-app:v1.5.0

Rollback:

image: ghcr.io/your-org/your-app:v1.4.9

Redeploy. Xong.


Quản lý secrets và cấu hình

Sai lầm phổ biến: bake secret vào image.

Không nên:

ENV DATABASE_PASSWORD=secret

Nên:

– App config qua environment variables. – Secrets lưu trong Portainer env/secret manager. – GitHub Secrets chỉ dùng cho CI/CD. – Không commit .env.

Phân tách:

Build-time config: dùng khi build image. – Runtime config: DB URL, API key, Redis URL, JWT secret.

Runtime config nên nằm ở Portainer Stack env:

environment:
  DATABASE_URL: ${DATABASE_URL}
  REDIS_URL: ${REDIS_URL}

Hoặc env_file.

Với team nhỏ, chỉ cần kỷ luật:

– Không hardcode secret. – Không in secret ra log. – Rotate token định kỳ. – Giới hạn quyền registry token. – Bật 2FA cho GitHub.


Dockerfile tối ưu cho CI/CD nhỏ

Dockerfile tốt → build nhanh, image nhỏ, ít lỗi production.

Ví dụ Node.js:

FROM node:20-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci

FROM node:20-alpine AS build WORKDIR /app COPY --from=deps /app/node_modules ./node_modules COPY . . RUN npm run build

FROM node:20-alpine AS runner WORKDIR /app ENV NODE_ENV=production COPY package*.json ./ RUN npm ci --omit=dev COPY --from=build /app/dist ./dist EXPOSE 3000 CMD ["node", "dist/main.js"]

Nguyên tắc:

– Multi-stage build. – Copy dependency file trước để tận dụng cache. – Không copy .git, node_modules, file local. – Có .dockerignore.

Ví dụ .dockerignore:

.git
node_modules
.env
dist
coverage
Dockerfile
docker-compose.yml

Healthcheck, logs, restart policy

Deploy thành công không có nghĩa app khỏe.

Compose nên có:

restart: always
healthcheck:
  test: ["CMD", "wget", "-qO-", "http://localhost:3000/health"]
  interval: 30s
  timeout: 5s
  retries: 3

App nên có endpoint:

GET /health

Logs nên xuất ra stdout/stderr. Không ghi log file trong container nếu không cần. Portainer đọc logs dễ hơn. Nếu hệ thống lớn dần, thêm Loki, Grafana, Promtail sau.

Restart policy:

restart: always cho service chính. – restart: unless-stopped nếu muốn container không tự bật lại sau khi stop thủ công.


Quy trình deploy thực tế cho team nhỏ

Một workflow gọn:

Staging

– Push vào develop. – GitHub Actions build image staging. – Push GHCR. – Trigger Portainer staging webhook. – QA kiểm tra.

Production

– Merge vào main. – Tạo Git tag vX.Y.Z. – GitHub Actions build image version. – Push GHCR. – Portainer production redeploy. – Theo dõi logs/healthcheck. – Nếu lỗi → rollback tag cũ.

Checklist trước production:

– Migration DB đã kiểm tra? – Env đầy đủ? – Image đã scan basic? – Healthcheck pass? – Backup DB gần nhất? – Có tag rollback?


Bảo mật tối thiểu nhưng cần có

Portainer thường nằm trên internet. Cần bảo vệ nghiêm túc.

Khuyến nghị:

– Đặt Portainer sau HTTPS. – Dùng mật khẩu mạnh. – Bật 2FA nếu có. – Giới hạn IP truy cập bằng firewall/VPN nếu được. – Không public Docker socket. – Không chia sẻ webhook URL. – Registry package nên private nếu app private. – Token GitHub chỉ cấp quyền tối thiểu.

Nếu dùng GHCR private, server cần login registry:

docker login ghcr.io

Portainer cũng cần registry credential để pull image private.


Khi nào nên nâng cấp lên giải pháp lớn hơn?

Bộ ba này phù hợp khi:

– 1–5 server. – Team nhỏ. – Deployment đơn giản. – Ít service. – Không cần autoscale phức tạp.

Nên cân nhắc Kubernetes/Nomad/ECS khi:

– Nhiều service phụ thuộc nhau. – Cần autoscaling. – Cần zero-downtime rollout chuẩn. – Multi-region. – Traffic lớn. – Cần policy/networking phức tạp.

Đừng tối ưu quá sớm. Docker Compose + Portainer có thể chạy tốt rất lâu nếu thiết kế hợp lý.


Kết luận thực tế

Workflow DevOps nhỏ tốt không cần hào nhoáng. Cần ổn định, lặp lại được, dễ rollback, ít thao tác tay.

Portainer + GitHub Actions + Docker Registry tạo nền tảng rất cân bằng:

– GitHub Actions → tự động build/push. – Registry → lưu artifact rõ version. – Portainer → quản lý deploy trực quan. – Docker Compose → cấu hình dễ đọc, dễ sửa.

Công thức khuyến nghị:

– Dùng GHCR làm registry. – Build image bằng GitHub Actions. – Tag bằng commit SHA/version. – Deploy qua Portainer Stack. – Trigger bằng webhook. – Config runtime bằng env. – Luôn có rollback tag. – Theo dõi logs/healthcheck sau deploy.

Bắt đầu đơn giản. Chuẩn hóa dần. Khi hệ thống lớn hơn, bạn vẫn giữ được nền tảng quan trọng nhất của DevOps: mỗi thay đổi đều có thể build, deploy, kiểm tra, rollback một cách có kiểm soát.

Tác giả

P T P

Chia sẻ

Bài viết liên quan

Bình luận (0)

Email của bạn sẽ không được hiển thị công khai.

Chưa có bình luận. Hãy là người đầu tiên!