Docker-образы
У каждого проекта — свой Dockerfile в корне, запускаемый на Kubernetes. CI собирает каждый образ, пушит в registry, а GitOps бампает тег в Helm-values (см. GitOps).
| приложение | база | собирает | запускается как | k8s-объект | порт |
|---|---|---|---|---|---|
api | node:22-alpine | NestJS → dist | node dist/main.js | Deployment + Service + HPA + Ingress (пул core) | 3333 |
app | node:22-alpine | Nuxt → .output | node .output/server/index.mjs | Deployment + Service + Ingress | 3000 |
admin | node:22-alpine | Nuxt → .output | node .output/server/index.mjs | Deployment + Service + Ingress | 3001 |
worker | Playwright/Chromium (heavy) | цикл агента + тулы | одна задача, потом выход | k8s Job (без Service) — на задачу | — |
Общие принципы
- Multi-stage сборки — стадия
build(полные зависимости + компиляция) и тонкаяruntime(только prod-зависимости + артефакты). Маленький финальный образ, быстрые пуллы. - Non-root юзер; пинованные теги базовых образов;
.dockerignore(безnode_modules,.git,dist). - Healthcheck для долгоживущих сервисов (Deployments); k8s
readinessProbe/livenessProbe. - Конфиг через env (12-factor); секреты не вшиваем — инжектит k8s в рантайме. Инфра-креды приходят из HashiCorp Vault через External Secrets Operator, а не из Sealed Secrets (см. GitOps); секреты агента резолвятся из Postgres.
- Один образ на папку репо — с двумя поправками к строке, которая тут была раньше. У
app/нетDockerfile, хотяhelm/agentfy-appвAgentfy/gitopsфиксирует реальный тегghcr.io/agentfy/agentfy-app, то есть этот образ собирается там, где не записано ни в одном из репозиториев. А уworker/их три:Dockerfile(shell-воркер),Dockerfile.browser(тот же образ плюс Chromium, AGNT2-212) иDockerfile.control— намеренно незахищённый образ, который существует только чтобы можно было показатьscripts/verify-image.shпадающим. Контрольный образ никогда не собирается для деплоя.
api — Core + эфемерный мозг
# build
FROM node:22-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npx prisma generate && npm run build
# runtime
FROM node:22-alpine AS runtime
WORKDIR /app
ENV NODE_ENV=production
COPY package*.json ./
RUN npm ci --omit=dev
COPY --from=build /app/dist ./dist
COPY --from=build /app/node_modules/.prisma ./node_modules/.prisma
USER node
EXPOSE 3333
CMD ["node", "dist/main.js"]api — это stateless, горизонтально масштабируемый Deployment (HPA скейлит реплики по нагрузке). «Эфемерный агент = api» значит нет пода на агента — каждый турн это stateless-запрос, который грузит стейт агента из Postgres и отвечает; общий флот api скейлится по трафику. (Литеральный под-на-задачу — это Job воркера.) Prisma-миграции не в образе: их планировали как PreSync-хук GitOps, а сегодня они выполняются в initContainer у Deployment api (helm/agentfy-api/templates/deployment.yaml в Agentfy/gitops).
app / admin — Nuxt
FROM node:22-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build # → .output (Nitro server build)
FROM node:22-alpine AS runtime
WORKDIR /app
ENV NODE_ENV=production
COPY --from=build /app/.output ./.output
USER node
EXPOSE 3000 # admin: 3001
CMD ["node", ".output/server/index.mjs"]SSR (Nitro Node-сервер) за Ingress. Если набор страниц полностью статичен — альтернатива nuxt generate → образ nginx:alpine, отдающий статику — та же форма Deployment.
worker — руки агента (heavy-образ)
Отличается от остальных: тяжёлая база с настоящей ОС — bash, файловая система и Chromium (Playwright) — плюс цикл агента + тул-исполнители + WS-клиент. Не долгоживущий сервер; образ выполняет одну задачу и выходит, используется k8s Job на задачу.
# Тяжёлая база с готовым Chromium + deps (образ Playwright, либо
# oven/bun:alpine + chromium, как в Dockerfile cleanslice/runtime).
FROM mcr.microsoft.com/playwright:v1.49.0-jammy
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
USER pwuser
# Подключается к Core по WS через SESSION_ID/CORE_WS_URL/SESSION_TOKEN, тянет
# задачу, гоняет bash/fs/browser тулы, выгружает результаты, затем выходит.
ENTRYPOINT ["node", "dist/worker.js"]- Берёт цикл агента + тул-исполнители из
cleanslice/runtime. - Запускается в Job:
restartPolicy: Never,ttlSecondsAfterFinished,emptyDir-workspace, read-only root fs (запись только/workspace), короткоживущий projected secret. См. Worker на Kubernetes. - Не управляется GitOps как Deployment — Argo держит только ref образа воркера + RBAC + шаблон NetworkPolicy + пресеты
RuntimeProfile; экземпляры Job создаются в рантайме.
Поток сборки и доставки
git push → docker build <app>/Dockerfile → push ghcr.io/agentfy/<app>:<sha>
→ бамп image.tag в Agentfy/gitops helm/<chart>/values.dev.yaml → ArgoCD синкаетВторая половина настоящая, и путь точный. Первая не подтверждена: ни в одном из репозиториев нет workflow, который собирал бы или пушил образ приложения, а в этом репозитории нет .github/ вообще, — при этом values-файлы несут реальные теги. См. Два репозитория.
Локальная разработка — те же Dockerfile через compose; прод — на Hetzner k8s. Через среды двигается единый артефакт (образ) — воспроизводимо и откатываемо.
Одно исключение из контекста <app>/ (AGNT2-248). Образ api собирается из КОРНЯ репозитория — docker build -f api/Dockerfile . — потому что каталог инструментов воркера, который api показывает модели до подъёма пода, генерируется из worker/src во время сборки. Исходники воркера здесь вход сборки и ни в один слой готового образа не попадают; .dockerignore в корне не даёт контексту стать всеми node_modules репозитория.