راهاندازی Angos بهعنوان OCI Registry، کش مرکزی Imageها و مقصد امن Gitea Actions
از نصب تکسروری با Docker Compose تا Mirror کردن Docker Hub و GHCR، احراز هویت بدون Secret ثابت با OIDC و انتشار Image از Workflowهای گیتیا.
01 معرفی و معماری
Angos یک رجیستری سازگار با OCI Distribution و Docker Registry است. علاوه بر نگهداری Imageهای خودتان، میتواند درخواست Pull را به رجیستری بالادستی بفرستد و Manifest و Blobهای دریافتشده را در Storage محلی Cache کند. احراز هویت OIDC و کنترل دسترسی با Policyهای CEL انجام میشود.
Build / Push
OIDC + CEL
Filesystem / S3
02 پیشنیازها و مقادیر نمونه
در همه مثالها از مقادیر زیر استفاده میکنیم. آنها را با محیط خود جایگزین کنید.
| مورد | مقدار نمونه | توضیح |
|---|---|---|
| دامنه Gitea | https://git.example.com | باید با ROOT_URL یکسان باشد. |
| دامنه Registry | https://registry.example.com | آدرس قابل دسترس برای Runner و Docker. |
| Issuer | https://git.example.com/api/actions/oidc | OIDC Provider گیتیا. |
| Audience | https://registry.example.com | شناسه سرویس مقصد Token. |
| Repository | platform/myapp | Repository مجاز برای Push. |
| Branch | refs/heads/main | Branch مجاز. |
- Docker Engine و Compose Plugin نصب باشد.
- دامنه Registry با TLS معتبر داشته باشید.
- Runner گیتیا به Registry دسترسی شبکهای داشته باشد.
- Angos بتواند Discovery و JWKS گیتیا را با HTTPS دریافت کند.
- نسخه Gitea شما OIDC برای Actions را پشتیبانی کند؛ Endpoint Discovery را آزمایش کنید.
03 نصب Angos با Docker Compose
۳.۱ ساخت پوشهها و مجوز Storage
mkdir -p angos/{config,secrets,data}
cd angos
# کانتینر Angos با UID 65534 اجرا میشود
sudo chown -R 65534:65534 dataساختار نهایی:
angos/ ├── docker-compose.yml ├── config/config.toml ├── secrets/token-service.toml └── data/
۳.۲ فایل docker-compose.yml
نمونه تکسروری. TLS در Reverse Proxy جلویی خاتمه مییابد و پورت داخلی فقط روی Loopback میزبان منتشر میشود.
services:
angos:
image: ghcr.io/project-angos/angos:latest
container_name: angos
command: ["-c", "/config/config.toml", "-c", "/secrets/token-service.toml", "server"]
restart: unless-stopped
ports:
- "127.0.0.1:18000:8000"
volumes:
- ./config:/config:ro
- ./secrets:/secrets:ro
- ./data:/data
healthcheck:
test: ["CMD", "curl", "-fsS", "http://localhost:8000/healthz"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
security_opt:
- no-new-privileges:truelatest برای سادگی استفاده شده است. در استقرار واقعی از Tag نسخه مشخص یا Digest استفاده کنید تا ارتقای ناخواسته رخ ندهد.۳.۳ پیکربندی پایه config/config.toml
[server] bind_address = "0.0.0.0" port = 8000 [blob_store.fs] root_dir = "/data" [ui] enabled = true name = "Platform OCI Registry" [global] max_concurrent_requests = 32 max_concurrent_cache_jobs = 8 # Docker Hub official images [repository."library"] immutable_tags = true immutable_tags_exclusions = ["^latest$", "^nightly.*$"] [[repository."library".upstream]] url = "https://registry-1.docker.io" # GitHub Container Registry [repository."ghcr.io"] immutable_tags = true [[repository."ghcr.io".upstream]] url = "https://ghcr.io" # Private images pushed by CI [repository."apps"]
مسیرهای نمونه:
- Docker Hub رسمی:
registry.example.com/library/nginx:1.27 - GHCR:
registry.example.com/ghcr.io/OWNER/IMAGE:TAG - Image خصوصی CI:
registry.example.com/apps/platform/myapp:TAG
۳.۴ TLS و Reverse Proxy
در این طرح Caddy یا Nginx درخواست HTTPS را دریافت کرده و به 127.0.0.1:18000 روی میزبان میفرستد. نمونه Caddyfile:
registry.example.com {
reverse_proxy 127.0.0.1:18000
}اگر Caddy داخل Docker است، 127.0.0.1 به خود کانتینر Caddy اشاره میکند، نه میزبان. در آن حالت Angos و Caddy را به یک Docker network مشترک متصل کنید و مقصد را با نام سرویس، مثلاً angos:8000 قرار دهید. پورت 8000 را در اینترنت عمومی منتشر نکنید.
۳.۵ اجرا و بررسی
docker compose config docker compose up -d docker compose ps docker compose logs -f angos curl -i http://127.0.0.1:18000/healthz curl -i http://127.0.0.1:18000/v2/
پاسخ /v2/ بسته به Policy ممکن است 200 یا Challenge احراز هویت باشد؛ مهم دریافت پاسخ Registry API است.
04 راهاندازی Pull-through Cache
کلاینت به جای رجیستری اصلی از Angos Pull میکند. در صورت نبود محتوا، Angos آن را از Upstream میگیرد و در Storage محلی نگهداری میکند.
۴.۱ کش Docker Hub
برای Imageهای رسمی Docker Hub، نمونه مستندات Angos از Repository با نام library استفاده میکند:
[repository."library"] immutable_tags = true immutable_tags_exclusions = ["^latest$", "^nightly.*$"] [[repository."library".upstream]] url = "https://registry-1.docker.io"
به جای Pull مستقیم:
docker pull nginx:1.27
از این مسیر استفاده کنید:
docker pull registry.example.com/library/nginx:1.27
برای دریافت Imageهای خصوصی یا محدودشده Docker Hub، Credential حسابی با مجوز فقط خواندن را در Upstream تعریف کنید:
[[repository."library".upstream]] url = "https://registry-1.docker.io" username = "DOCKERHUB_READONLY_USER" password = "DOCKERHUB_ACCESS_TOKEN"
-c دوم به Angos معرفی کنید.۴.۲ کش GitHub Container Registry
[repository."ghcr.io"] immutable_tags = true [[repository."ghcr.io".upstream]] url = "https://ghcr.io"
نمونه:
docker pull registry.example.com/ghcr.io/OWNER/IMAGE:TAG
برای GHCR خصوصی، Credential دارای مجوز خواندن Package را در Upstream تعریف کنید؛ برای Image عمومی معمولاً Credential لازم نیست.
۴.۳ نامگذاری و مسیرها
| مبدأ | Image اصلی | مسیر Angos |
|---|---|---|
| Docker Hub رسمی | nginx:1.27 | registry.example.com/library/nginx:1.27 |
| GHCR | ghcr.io/owner/app:v1 | registry.example.com/ghcr.io/owner/app:v1 |
[repository."..."] نقطه شروع فضای نام در URL رجیستری است. برای Docker Hub رسمی، مثال مستندات از library استفاده میکند. مسیر سایر Namespaceها را با Pull آزمایشی اعتبارسنجی کنید.۴.۴ تست Cache و Tagها
# Pull اول: معمولاً Cache miss docker pull registry.example.com/library/alpine:3.20 # Pull بعدی: از نسخه ذخیرهشده استفاده میشود docker pull registry.example.com/library/alpine:3.20 # مشاهده Metricهای Pull-through curl -s https://registry.example.com/metrics | grep angos_pull_through_total
- Immutable: برای Tag نسخهای مانند
1.27.2بررسی Upstream را حذف میکند. - Mutable: برای
latestمیتواند Upstream را برای تغییر Digest بررسی کند. - Exclusion: الگوی
^latest$، latest را از Immutable بودن خارج میکند.
Metric angos_pull_through_total نتیجههایی مانند hit، miss، refresh و stale را گزارش میکند.
05 اتصال Gitea Actions به Angos با OIDC
Gitea برای Job یک JWT کوتاهعمر صادر میکند. Angos امضای آن را با JWKS گیتیا بررسی میکند و بر اساس Claimها تصمیم میگیرد کدام Repository و Branch اجازه Push داشته باشند.
۵.۱ Issuer و Discovery
اگر ROOT_URL گیتیا برابر https://git.example.com/ باشد، Issuer چنین است:
https://git.example.com/api/actions/oidc
Discovery Endpoint:
https://git.example.com/api/actions/oidc/.well-known/openid-configuration
از میزبان Angos یا محیطی با دسترسی شبکه مشابه بررسی کنید:
curl -fsS \
https://git.example.com/api/actions/oidc/.well-known/openid-configuration \
| jq '{issuer, jwks_uri, claims_supported}'مقدار issuer باید دقیقاً با تنظیم Angos برابر باشد و Angos باید به jwks_uri دسترسی داشته باشد.
<APP_DATA_PATH>/jwt/actions_oidc.pem نگه میدارد. این فایل باید پایدار باشد. در چند Instance پشت Load Balancer، همه باید همین فایل را به اشتراک بگذارند.۵.۲ Audience
Audience پیشفرض Gitea معمولاً <ROOT_URL>/<owner> است؛ اما Workflow میتواند Audience دیگری درخواست کند. این راهنما از https://registry.example.com استفاده میکند و Angos آن را با required_audience محدود میکند.
۵.۳ تعریف Provider و Policy در Angos
به config.toml اضافه کنید:
[auth.oidc.gitea-actions] issuer = "https://git.example.com/api/actions/oidc" required_audience = "https://registry.example.com" required_claims = ["repository", "actor", "ref", "sub"]
نام gitea-actions شناسه Provider است و در Docker Login بهعنوان Username استفاده میشود.
Policy سراسری: فقط Health/Metrics و Repositoryهایی که Policy اختصاصی دارند پذیرفته شوند.
[global.access_policy] default = "deny" rules = [ "request.action in ['healthz', 'readyz', 'metrics']", "has_repository_policy()" ]
برای فضای خصوصی apps، Push را فقط برای Repository و Branch مشخص مجاز کنید:
[repository."apps".access_policy]
default = "deny"
rules = [
'''identity.oidc != null &&
identity.oidc.provider_name == "gitea-actions" &&
identity.oidc.claims["repository"] == "platform/myapp" &&
identity.oidc.claims["ref"] == "refs/heads/main"'''
]برای Cache عمومی Docker Hub و GHCR، Pull ناشناس را فقط برای Manifest و Blob مجاز کنید:
[repository."library".access_policy] default = "deny" rules = [ "request.action in ['get-manifest', 'get-blob']" ] [repository."ghcr.io".access_policy] default = "deny" rules = [ "request.action in ['get-manifest', 'get-blob']" ]
default = "deny"، Ruleها مجوزدهندهاند و اگر حداقل یکی True شود دسترسی مجاز است. با default = "allow"، Ruleها نقش Deny دارند. این راهنما از الگوی default-deny استفاده میکند.Policy فضای apps تمام عملیات Registry را به همان Job مجاز محدود میکند. اگر Deploy یا توسعهدهندگان باید Pull کنند، Ruleهای Read را با هویت جداگانه و حداقل دسترسی اضافه کنید.
۵.۴ Token Service برای Push طولانی
توکن OIDC گیتیا طبق مستندات جاری پنج دقیقه اعتبار دارد. Token Service آن را در شروع عملیات به Registry Token تبدیل میکند تا Push طولانی به اعتبار JWT اولیه وابسته نماند.
فایل مجزای secrets/token-service.toml:
[auth.token_service] secret_key = "REPLACE_WITH_BASE64_SECRET" ttl_secs = 1800
تولید Secret:
openssl rand -base64 32
Rule مربوط به تبادل Token را به Policy سراسری اضافه کنید:
[global.access_policy] default = "deny" rules = [ "request.action in ['healthz', 'readyz', 'metrics']", "request.action == 'get-token' && identity.oidc != null", "has_repository_policy()" ]
در اولین درخواست، Docker ممکن است Challenge دریافت کند و به Token Service مراجعه کند. اگر get-token مجاز نباشد، Login یا Push شکست میخورد. TTL را متناسب با طول Push انتخاب کنید؛ Registry Token تا پایان TTL قابل ابطال نیست.
06 Workflow پایه Gitea Actions
فایل .gitea/workflows/publish.yml. پس از Push به main، JWT گرفته میشود، Docker Login انجام میشود و Image به Angos فرستاده میشود.
name: Build and Publish
on:
push:
branches:
- main
jobs:
publish:
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Request OIDC token
id: oidc
shell: bash
run: |
set -euo pipefail
TOKEN="$(curl --fail --silent --show-error \
--header "Authorization: Bearer ${ACTIONS_ID_TOKEN_REQUEST_TOKEN}" \
"${ACTIONS_ID_TOKEN_REQUEST_URL}&audience=https://registry.example.com" \
| jq -er '.value')"
echo "::add-mask::${TOKEN}"
echo "token=${TOKEN}" >> "${GITHUB_OUTPUT}"
- name: Login to Angos
env:
REGISTRY_TOKEN: ${{ steps.oidc.outputs.token }}
run: |
printf '%s' "$REGISTRY_TOKEN" | docker login \
registry.example.com \
--username gitea-actions \
--password-stdin
- name: Build image
run: |
docker build \
--tag registry.example.com/apps/platform/myapp:${{ github.sha }} .
- name: Push image
run: |
docker push \
registry.example.com/apps/platform/myapp:${{ github.sha }}id-token: write فقط اجازه درخواست OIDC Token را میدهد؛ بهتنهایی مجوز Push به Registry یا نوشتن در Repository ایجاد نمیکند. توکن را نزدیک Login و Push دریافت کنید.07 مثالهای کاربردی در Actions
۷.۱ Docker CLI
در نمونه پایه، ابتدا Build و سپس Token/Login/Push انجام میشود. در Buildهای طولانی Token را قبل از Build دریافت نکنید.
- name: Build image
run: docker build -t registry.example.com/apps/platform/myapp:${{ github.sha }} .
- name: Get OIDC token
id: oidc
run: |
TOKEN=$(curl -fsS \
-H "Authorization: Bearer ${ACTIONS_ID_TOKEN_REQUEST_TOKEN}" \
"${ACTIONS_ID_TOKEN_REQUEST_URL}&audience=https://registry.example.com" \
| jq -er '.value')
echo "::add-mask::$TOKEN"
echo "token=$TOKEN" >> "$GITHUB_OUTPUT"
- name: Docker login
env:
REGISTRY_TOKEN: ${{ steps.oidc.outputs.token }}
run: |
printf '%s' "$REGISTRY_TOKEN" | docker login registry.example.com \
-u gitea-actions --password-stdin
- name: Push
run: docker push registry.example.com/apps/platform/myapp:${{ github.sha }}۷.۲ Docker Buildx و Multi-platform
- name: Set up Buildx
uses: docker/setup-buildx-action@v3
- name: Get OIDC token
id: oidc
run: |
TOKEN=$(curl -fsS \
-H "Authorization: Bearer ${ACTIONS_ID_TOKEN_REQUEST_TOKEN}" \
"${ACTIONS_ID_TOKEN_REQUEST_URL}&audience=https://registry.example.com" \
| jq -er '.value')
echo "::add-mask::$TOKEN"
echo "token=$TOKEN" >> "$GITHUB_OUTPUT"
- name: Login to Angos
uses: docker/login-action@v3
with:
registry: registry.example.com
username: gitea-actions
password: ${{ steps.oidc.outputs.token }}
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: registry.example.com/apps/platform/myapp:${{ github.sha }}push: false Build کنید و Push را در مرحله جدا انجام دهید یا Token Service را فعال کنید.۷.۳ Pull از Cache در Workflow
اگر Pull عمومی Cache را مجاز کردهاید، Login لازم نیست:
- name: Pull base image through Angos
run: docker pull registry.example.com/library/alpine:3.20
- name: Build
run: docker build \
--build-arg BASE_IMAGE=registry.example.com/library/alpine:3.20 \
-t registry.example.com/apps/platform/myapp:${{ github.sha }} .Dockerfile نمونه:
FROM registry.example.com/library/alpine:3.20 WORKDIR /app COPY . . CMD ["./app"]
۷.۴ چند Repository یا Branch
برای مجاز کردن چند Repository، Claim را با Regex محدود کنید:
[repository."apps".access_policy]
default = "deny"
rules = [
'''identity.oidc != null &&
identity.oidc.provider_name == "gitea-actions" &&
identity.oidc.claims["repository"].matches("^platform/(api|web|worker)$") &&
identity.oidc.claims["ref"] == "refs/heads/main"'''
]میتوانید Claim مربوط به workflow را هم محدود کنید. برای تصمیم نهایی، Claimهای واقعی Token صادرشده از نسخه Gitea خود را بررسی کنید؛ مقدار کامل JWT را در Log چاپ نکنید.
08 نگهداری و پایش
۸.۱ فرمانهای روزمره
docker compose ps docker compose logs -f angos curl -fsS https://registry.example.com/healthz curl -i https://registry.example.com/v2/ # Storage consistency check docker compose exec angos angos -c /config/config.toml scrub # Retention / reclaim docker compose exec angos angos -c /config/config.toml prune
عملیات scrub و prune را با برنامه پشتیبانگیری مناسب اجرا کنید و پیش از حذف دادهها رفتار نسخه خود را بررسی کنید.
۸.۲ Durable Cache Jobs و Worker
در نصب تکسروری، [global.job_queue] لازم نیست؛ خود Process سرور صف Cache-fill را Drain میکند و Jobها در Backend متادیتا پایدار میمانند. برای چند Replica یا Worker مستقل، Durable Queue را فعال کنید و دستکم یک Process با فرمان angos worker اجرا کنید. سرور و Worker باید به Backend متادیتا و Blob مشترک دسترسی داشته باشند.
۸.۳ Redis چه زمانی لازم است؟
Redis برای شروع تکReplica ضروری نیست. در چند Replica میتواند Cacheهای Link، Token و Key را به اشتراک بگذارد. خود محتوای Image در Blob Storage نگهداری میشود؛ Redis جای Blob Storage نیست.
۸.۴ Metricهای مفید
| Metric | کاربرد |
|---|---|
angos_pull_through_total | Cache hit، miss، refresh و stale برای Manifest و Blob. |
angos_job_queue_pending | Jobهای آماده در صف، در صورت فعال بودن Durable Queue. |
angos_job_queue_failed | Jobهای منتقلشده به Dead-letter. |
auth_attempts_total | تلاشهای موفق و ناموفق احراز هویت. |
09 عیبیابی رایج
| نشانه | علت احتمالی | بررسی |
|---|---|---|
| 401 هنگام Docker Login | Issuer، Audience یا JWT نامعتبر. | Discovery، required_audience، ساعت سیستم و نام Provider. |
| 403 هنگام Push | Policy اجازه نمیدهد. | Claimهای repository، ref و نام Provider. |
| OIDC endpoint پیدا نمیشود | نسخه Gitea قدیمی یا مسیر اشتباه. | نسخه Gitea، ROOT_URL و Discovery URL. |
| JWT معتبر است اما امضا رد میشود | Angos به JWKS دسترسی ندارد یا کلید تغییر کرده. | دسترسی کانتینر Angos به jwks_uri و پایداری کلید. |
| Push طولانی قطع میشود | OIDC پنجدقیقهای منقضی شده. | Token Service و زمان دریافت Token. |
| Pull به Upstream نمیرسد | مسیر یا URL upstream نادرست. | نام Repository و لاگ Angos. |
| Permission denied روی data | مالک پوشه اشتباه است. | sudo chown -R 65534:65534 data |
| TLS نامعتبر | گواهی ناقص یا دامنه اشتباه. | زنجیره CA و SAN؛ اعتبارسنجی TLS را خاموش نکنید. |
iss، aud، sub، repository و ref را در محیط امن بررسی کنید. JWT خودش Credential قابل استفاده است.10 چکلیست امنیت Production
- Registry فقط از طریق HTTPS و گواهی معتبر ارائه شود.
- پورت داخلی Angos روی اینترنت عمومی منتشر نشود.
- از Tag ثابت یا Digest برای Image خود Angos استفاده شود.
- Policy سراسری با
default = "deny"تنظیم شود. - Push به Repository و Branch مشخص محدود شود.
- Audience اختصاصی با
required_audienceاعتبارسنجی شود. - کلید OIDC گیتیا در Volume پایدار حفظ شود.
- Secret مربوط به Token Service خارج از Git نگهداری شود.
- TTL توکن Registry متناسب با Push باشد؛ تا انقضا قابل ابطال نیست.
- Credentialهای Upstream فقط مجوز Pull داشته باشند.
- از Storage نسخه پشتیبان تهیه و بازیابی آن آزمایش شود.
- Metricها و لاگهای احراز هویت پایش شوند.
11 منابع رسمی
متن راهنما و نمونهکدها داخل همین فایل هستند. لینکهای زیر هنگام اتصال به اینترنت برای مطالعه جزئیات و تغییرات نسخهها قابل استفادهاند.
- Angos — وبسایت رسمی
- Angos — Deploy with Docker Compose
- Angos — GitHub Actions OIDC (الگوی OIDC و Token Service)
- Angos — Push from GitHub Actions (Docker، Buildx و Kaniko)
- Angos — Generic OIDC
- Angos — Authentication and Authorization
- Angos — Durable Cache Jobs
- Angos — Metrics
- Gitea — OpenID Connect for Actions
- Gitea — Actions Quick Start