Пример сборки backend-сервиса
Dockerfile
FROM python:3.13-slim-trixie AS builder
RUN apt update -y; apt install -y libpq-dev gcc; rm -rf /var/lib/apt/lists/*;
RUN pip install poetry
ENV POETRY_NO_INTERACTION=1 \
POETRY_VIRTUALENVS_IN_PROJECT=1 \
POETRY_VIRTUALENVS_CREATE=1 \
POETRY_CACHE_DIR=/tmp/poetry_cache
WORKDIR /opt
COPY pyproject.toml poetry.lock ./
RUN poetry install --without dev --no-root && rm -rf $POETRY_CACHE_DIR
FROM python:3.13-slim-trixie AS runtime
ENV VIRTUAL_ENV=/opt/.venv PATH="/opt/.venv/bin:$PATH"
RUN apt update -y; apt install -y libpq-dev; rm -rf /var/lib/apt/lists/*;
COPY --from=builder ${VIRTUAL_ENV} ${VIRTUAL_ENV}
WORKDIR /opt
COPY src src
COPY docker/entrypoint.sh entrypoint.sh
RUN chmod +x entrypoint.sh
ENTRYPOINT ["/opt/entrypoint.sh"]
Dockerfile обычно лежит в ./docker/Dockerfile (указывается в DOCKERFILE_PATH).
Как переменные попадают в билд
В workflow.rules для ветки задаётся:
variables:
SERVICE_NAME: bi-backend
DOCKERFILE_PATH: ./docker/Dockerfile
BUILD_ARGS: ""
CI_TRIGGER_SOURCE: app
И для конкретного окружения:
- if: '$CI_COMMIT_BRANCH == "stage"'
variables:
STAND: stage
NAMESPACE: planning
RELEASE_NAME: ${SERVICE_NAME}
HELM_SET_ARGS: >-
--set universal-chart.services.main.image.name.stage=${IMAGE_NAME}
--set universal-chart.global.env=stage
BUILD_ARGS — строка, вставляемая в docker build:
docker build -f ${DOCKERFILE_PATH} ${BUILD_ARGS} -t ${IMAGE_NAME} .
Если нужно передать аргументы в Dockerfile:
BUILD_ARGS: "--build-arg CI_COMMIT_SHORT_SHA=${CI_COMMIT_SHORT_SHA}"
Формирование IMAGE_NAME
IMAGE_NAME = ${CR}/${RELEASE_NAME}:${STAND}_${CI_COMMIT_SHORT_SHA}
Пример: cr.yandex/crp.../bi-backend:stage_abc123def
Этот тег пушится в registry и подставляется в Helm через HELM_SET_ARGS.
Полный flow сборки образа
-
workflow.rules подставляет переменные для ветки (
STAND,NAMESPACE,HELM_SET_ARGS) -
Опциональные джобы
lint/typecheck— если ошибка, pipeline останавливается. Поведение можно переопределить с помощью опцииallow_failure. -
build (extends
.base_docker): .base_dockerделаетdocker loginи выставляет переменнуюDOCKER_BUILDKIT=1-
Выполняется:
bash docker build -f ./docker/Dockerfile ${BUILD_ARGS} -t ${IMAGE_NAME} . docker push ${IMAGE_NAME} -
build_chart пакует и пушит Helm chart в
HELM_REGISTRY -
deploy job:
bash helm upgrade -i bi-backend .helm --atomic -n=${NAMESPACE} \ -f .helm/values.yaml ${HELM_SET_ARGS}
где HELM_SET_ARGS содержит:
--set universal-chart.services.main.image.name.stage=${IMAGE_NAME}
Примеры кастомных джоб
lint:
stage: lint
image: ghcr.io/astral-sh/uv:0.7.3-python3.13-bookworm-slim
script:
- uv sync --locked
- uv run ruff check src
- uv run ruff format src --check
rules:
- if: '$CI_COMMIT_BRANCH'
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
typecheck:
stage: lint
image: ghcr.io/astral-sh/uv:0.7.3-python3.13-bookworm-slim
script:
- uv sync --locked
- uv run mypy src
rules:
- if: '$CI_COMMIT_BRANCH'
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
allow_failure: true
Эти джобы работают отдельно от workflow — у них свои rules и среда выполнения.
values.yaml для backend
universal-chart:
global:
env: stage
services:
main:
enabled: true
deployment:
enabled: true
name:
_default: bi-backend
replicaCount:
_default: 1
production: 3
port:
_default: 80
resources:
requests:
memory:
_default: "1024Mi"
cpu:
_default: "500m"
image:
name:
_default: cr.yandex/crp3ccidau046kdj8g9q/bi-backend
pullPolicy:
_default: IfNotPresent
service:
enabled: true
type:
_default: ClusterIP
port:
_default: 8080
targetPort:
_default: 8000
envs:
- name: SETTINGS_DEBUG
value:
_default: "0"
- name: AUTH_HOST
value:
_default: "https://stage.sarex.io"
preprod: "https://preprod.sarex.io"
production: "https://lk.sarex.io"
secretEnvs:
- name: DB_PASSWORD
secretName:
_default: "ya-pg-secret"
secretKey: "password"
volumes:
_default:
- name: ya-ca-cert
mountPath:
_default: /opt/config
readOnly:
_default: true
configMap:
name:
_default: ya-ca-cert
items:
- key: ca.crt
path:
_default: ca.crt
commitSha: ""
gitlabUri: ""
gitlabJobUrl: ""
owner: "team-abc"
ingress:
enabled: false
Важно: секреты и ConfigMap создаются внешними средствами, чарт только ссылается на них.