ZIM-НИЙ SAAALEЗимние скидки: до −50% на старт и −20% на продление
до 31.01.2026 Подробнее
Выберите продукт

CI/CD артефакты в S3 Object Storage: как раздавать через Nginx с правильным Cache-Control

Артефакты CI/CD всё чаще выносят в S3‑совместимый Object Storage: билды, фронтенд‑статику, отчеты, архивы. Чтобы избежать проблем с кэшем браузера и медленной раздачей, полезно закрыть хранилище за Nginx, настроить понятный layout ключей и разные Cache-Control для версионированных и latest‑путей.
CI/CD артефакты в S3 Object Storage: как раздавать через Nginx с правильным Cache-Control

CI/CD давно перестал быть чем‑то экзотическим: пайплайны собирают приложения, гоняют тесты, формируют образы, архивы, статические бандлы фронтенда, отчеты. Всё это — артефакты (artifacts), и их нужно где‑то хранить и удобно раздавать.

Локальный диск GitLab/GitHub Runner’а или CI‑сервера быстро превращается в свалку, а масштабирование осложняется. Логичный шаг — вынести артефакты в S3‑совместимый Object Storage и раздавать через Nginx.

Дальше начинаются нюансы: права доступа, подписи ссылок, Cache-Control, разное время жизни кэша для «вечных» версионированных файлов и «живых» ссылок вида latest. В статье разберем схему end‑to‑end: от загрузки артефактов из CI до раздачи их пользователям и системам через Nginx с корректным кэшированием.

Зачем вообще хранить CI/CD артефакты в S3 Object Storage

Object Storage (S3‑совместимый) хорошо ложится на типичный профиль использования артефактов:

  • Горизонтальное масштабирование: нет привязки к диску конкретного CI‑сервера или веб‑нод.
  • Дешевая емкость по сравнению с SSD внутри VDS, плюс удобная политика ретеншна.
  • Простой API: стандартный протокол S3, множество клиентов (awscli, s5cmd, rclone и т.д.).
  • Надежность и резервирование: сам сервис заботится о репликации и целостности.

CI/CD здесь особенно выигрывает, потому что артефактов много, они относительно крупные, но читаются не постоянно, а всплесками — при релизах, откатах, деплоях в новые среды.

Типы артефактов в CI/CD и их требования

Чтобы грамотно построить схему с S3 и Nginx, полезно разделить артефакты по типам. От этого зависят и политика хранения, и Cache-Control.

1. Билды приложений и образы

Это .tar.gz, .zip, jar/war, docker‑образы (если вы бэкапите слои), бинарники Go/Node/PHP‑расширений. Основные свойства:

  • жесткая привязка к версии (commit SHA, tag, build number);
  • обычно не меняются после загрузки (immutable);
  • хотим долгое хранение и агрессивное кэширование.

Такие артефакты идеально подходят под «вечные» ключи в S3 и максимально жирный Cache-Control через Nginx.

2. Статические ассеты фронтенда

JS/CSS/изображения, которые билдит frontend‑пайплайн. Здесь обычно уже встроены практики версионирования по содержимому (content hash в имени файла):

  • app.3f4a1b7.js
  • styles.91c2d0.css

Ожидается, что:

  • файлы с хешем никогда не меняются после выкладки;
  • их можно кэшировать и в браузере, и в CDN практически бесконечно;
  • логика обновления делается через обновление «манифеста» или HTML, где меняются ссылки на новые версии.

Object Storage выступает как хранилище статики, а Nginx — как edge (или origin), который правильно расставляет заголовки кэширования.

3. Отчеты и логи

JUnit‑отчеты, coverage HTML, performance‑репорты, артефакты линтеров. У них другой профиль:

  • чаще всего нужны недолго (дни/недели);
  • доступ к ним нужен в основном внутри команды/CI UI;
  • агрессивное кэширование обычно не критично, но и не обязательно.

Тут важнее политика ретеншна и удобство доступа, чем тонкая настройка Cache-Control.

4. «Живые» алиасы: latest, stable и т.п.

Опасная и вечно проблемная категория:

  • URLs вроде releases/latest/app.tar.gz или stable/docker-compose.yml всегда указывают на последнюю версию;
  • физически в S3 это либо объект, который перезаписывается, либо редирект на версионированный объект;
  • кэширование должно быть аккуратным: не хотим, чтобы клиенты сутками видели старую версию.

Для таких ссылок настройки Nginx по Cache-Control будут отличаться от «вечных» версионированных артефактов.

Схема пайплайна CI/CD с выгрузкой артефактов в S3 и раздачей через Nginx

Базовая схема: CI → S3 → Nginx → клиенты

Рассмотрим типичную архитектуру, к которой мы дальше будем привязывать конфиги:

  1. GitLab CI, GitHub Actions или другой CI‑движок собирает артефакты.
  2. Пайплайн загружает артефакты в S3‑совместимый Object Storage по ключам вида:
    artifacts/<project>/<env>/<commit>/<filename>.
  3. Nginx на вашем сервере (или нескольких серверах) работает как reverse‑proxy к S3.

Внешние клиенты общаются только с Nginx; S3 не «торчит» наружу, а доступ к нему закрыт firewall’ом или приватной сетью.

  1. Nginx отвечает за авторизацию (если надо), заголовки Cache-Control, логи и ограничения скорости.

Плюсы такого подхода:

  • единая точка входа и контроля доступа;
  • гибкая настройка кэширования в зависимости от пути и типов артефактов;
  • упрощенная миграция backend’а (при смене Object Storage ничего не меняем у клиентов, только в Nginx).
FastFox VDS
Облачный VDS-сервер в России
Аренда виртуальных серверов с моментальным развертыванием инфраструктуры от 195₽ / мес

Как загружать артефакты в S3 из CI/CD

У разных CI‑систем своя специфика, но общая идея одна: после этапа сборки запускаем шаг, который складывает артефакты в Object Storage с предсказуемым layout’ом ключей.

Пример с GitLab CI и awscli

artifacts_s3:
  stage: deploy
  image: amazon/aws-cli:2.15.0
  script:
    - export AWS_ACCESS_KEY_ID="$S3_ACCESS_KEY"
    - export AWS_SECRET_ACCESS_KEY="$S3_SECRET_KEY"
    - export AWS_DEFAULT_REGION=ru-1
    - aws s3 cp build/app.tar.gz s3://ci-artifacts/myapp/prod/$CI_COMMIT_SHA/app.tar.gz
  only:
    - main

Точно так же можно использовать rclone или s5cmd. Главное — выработать стабильную схему ключей, которая:

  • включает commit SHA или build ID;
  • четко разделяет окружения (prod, stage, dev);
  • не меняется от релиза к релизу (чтобы Nginx‑конфиг не приходилось переписывать).

Версионированные и алиасные пути

Рекомендация по layout’у:

  • myapp/prod/versions/<commit>/app.tar.gz — реальный артефакт, immutable;
  • myapp/prod/latest/app.tar.gz — «указатель» на последнюю версию.

Роль «указателя» можно реализовать по‑разному:

  • просто перезаписывать сам файл latest/app.tar.gz на новый content; или
  • хранить там небольшой JSON/текстовый файл с commit SHA, а реальный artifact скачивать по версионированному пути.

Для нас, в контексте Nginx и Cache-Control, важно только то, что:

  • все, что лежит в /versions/ — immutable, можно кэшировать «навсегда»;
  • все, что лежит в /latest/ — меняется, кэшировать нужно аккуратно и недолго.

Базовый Nginx как reverse‑proxy к S3

Предположим, у нас есть S3 endpoint s3.internal.local и bucket ci-artifacts. Nginx должен принимать запросы вида:

  • https://artifacts.example.com/myapp/prod/versions/<commit>/app.tar.gz
  • https://artifacts.example.com/myapp/prod/latest/app.tar.gz

и проксировать их в S3 на:

  • https://s3.internal.local/ci-artifacts/myapp/prod/versions/<commit>/app.tar.gz

Простейшая конфигурация:

upstream s3_backend {
    server s3.internal.local:443;
    keepalive 32;
}

server {
    listen 443 ssl http2;
    server_name artifacts.example.com;

    # SSL, логирование и т.д. опускаем ради краткости

    location / {
        proxy_pass https://s3_backend/ci-artifacts$request_uri;
        proxy_set_header Host s3.internal.local;

        # Базовые заголовки кэширования можно переопределить ниже
    }
}

Такой вариант уже будет работать, но оставляет нас один на один с Cache-Control, который отдает сам S3. Чаще всего его либо нет, либо он слишком общий, и нам нужна более тонкая настройка.

Конфигурация Nginx с настройкой Cache-Control и proxy_cache для путей артефактов S3

Cache-Control для артефактов: общие принципы

Во многих проектах в этот момент начинают страдать от странного поведения кэша. Полезно сформулировать несколько жестких правил.

1. Версионированные артефакты — максимально агрессивный кэш

Если URL содержит commit SHA, build ID или content hash, и вы гарантируете, что содержимое по этому URL никогда не изменится, смело ставьте:

Cache-Control: public, max-age=31536000, immutable

Это:

  • разрешает кэширование в браузерах и промежуточных прокси;
  • говорит, что кэш можно хранить до года;
  • через immutable сигнализирует браузеру, что не нужно проверять обновления при навигации.

2. «Живые» алиасы — короткий, но не нулевой кэш

Для /latest/, /stable/ и прочих алиасов обычно разумно:

Cache-Control: public, max-age=60

или даже короче, если вы часто меняете версии, но не хотите ломиться в origin при каждом F5.

3. Отчеты — зависит от сценария

Если отчеты читают только через CI UI, часто достаточно:

Cache-Control: private, max-age=3600

Если же вы публикуете публичные отчеты (например, performance для маркетинга), можно использовать публичный кэш.

Настраиваем Cache-Control в Nginx по путям

Вариант 1 — полностью игнорировать HTTP‑заголовки S3 и задавать политику в Nginx. Пример с разным временем жизни для /versions/ и /latest/:

server {
    listen 443 ssl http2;
    server_name artifacts.example.com;

    set $s3_backend_host s3.internal.local;

    location / {
        proxy_pass https://s3_backend/ci-artifacts$request_uri;
        proxy_set_header Host $s3_backend_host;

        # Чистим исходные заголовки кэша от S3
        proxy_hide_header Cache-Control;
        proxy_hide_header Expires;

        # Значения по умолчанию (если не совпало ни одно из спец-правил)
        add_header Cache-Control "private, max-age=3600" always;
    }

    # Версионированные артефакты
    location ~ ^/[^/]+/[^/]+/versions/ {
        proxy_pass https://s3_backend/ci-artifacts$request_uri;
        proxy_set_header Host $s3_backend_host;

        proxy_hide_header Cache-Control;
        proxy_hide_header Expires;

        add_header Cache-Control "public, max-age=31536000, immutable" always;
    }

    # Алиасы latest/stable
    location ~ ^/[^/]+/[^/]+/(latest|stable)/ {
        proxy_pass https://s3_backend/ci-artifacts$request_uri;
        proxy_set_header Host $s3_backend_host;

        proxy_hide_header Cache-Control;
        proxy_hide_header Expires;

        add_header Cache-Control "public, max-age=60" always;
    }
}

Здесь мы:

  • используем общий upstream;
  • делаем отдельные location’ы для immutable и алиасных путей;
  • насильно проставляем Cache-Control, независимо от ответа S3.

Если вы уже используете map‑ы для кэш‑политик по расширениям, можно комбинировать этот подход с layout’ом путей. Подробный пример map‑конфигурации можно посмотреть в материале оптимизация WebP/AVIF и кэширования в Nginx через map.

Добавляем proxy_cache на стороне Nginx

Если у вас много запросов к одним и тем же артефактам, имеет смысл включить кеширование ответов S3 на уровне Nginx, чтобы:

  • снизить нагрузку на Object Storage;
  • ускорить отдачу часто скачиваемых билдов.

Пример конфигурации кеша:

proxy_cache_path /var/cache/nginx/s3_cache levels=1:2 keys_zone=s3_cache:128m max_size=10g inactive=7d use_temp_path=off;

proxy_cache_key "$scheme://$host$request_uri";

И использование в сервере:

server {
    listen 443 ssl http2;
    server_name artifacts.example.com;

    location / {
        proxy_pass https://s3_backend/ci-artifacts$request_uri;
        proxy_set_header Host s3.internal.local;

        proxy_cache s3_cache;
        proxy_cache_lock on;

        proxy_cache_valid 200 301 302 60m;
        proxy_cache_valid 404 1m;

        add_header X-Cache-Status $upstream_cache_status always;
    }
}

Чтобы не нарушать общую логику, Nginx по умолчанию уважает Cache-Control и Expires от backend’а. В нашем случае мы сами проставляем эти заголовки в зависимости от пути, и proxy_cache будет им следовать.

Разный срок жизни кэша для immutable и latest

Если хочется еще более явно разнести политику для immutable и latest, можно использовать разные proxy_cache_valid и даже разные зоны.

proxy_cache_path /var/cache/nginx/s3_versions levels=1:2 keys_zone=s3_versions:64m max_size=5g inactive=30d;
proxy_cache_path /var/cache/nginx/s3_latest levels=1:2 keys_zone=s3_latest:64m max_size=2g inactive=1d;

server {
    listen 443 ssl http2;
    server_name artifacts.example.com;

    location ~ ^/[^/]+/[^/]+/versions/ {
        proxy_pass https://s3_backend/ci-artifacts$request_uri;
        proxy_set_header Host s3.internal.local;

        proxy_cache s3_versions;
        proxy_cache_lock on;

        proxy_hide_header Cache-Control;
        proxy_hide_header Expires;
        add_header Cache-Control "public, max-age=31536000, immutable" always;
        add_header X-Cache-Status $upstream_cache_status always;
    }

    location ~ ^/[^/]+/[^/]+/(latest|stable)/ {
        proxy_pass https://s3_backend/ci-artifacts$request_uri;
        proxy_set_header Host s3.internal.local;

        proxy_cache s3_latest;
        proxy_cache_lock on;

        proxy_hide_header Cache-Control;
        proxy_hide_header Expires;
        add_header Cache-Control "public, max-age=60" always;
        add_header X-Cache-Status $upstream_cache_status always;
    }
}

Получается многоуровневое кэширование:

  • proxy_cache на Nginx;
  • кэш браузера/прокси, управляемый Cache-Control.

Более продвинутые сценарии с TTL, зависящими от подписи ссылки, можно строить на базе secure_link. Для вдохновения пригодится статья использование Nginx secure_link и управления временем жизни кэша.

Виртуальный хостинг FastFox
Виртуальный хостинг для сайтов
Универсальное решение для создания и размещения сайтов любой сложности в Интернете от 95₽ / мес

ETag и If-None-Match для артефактов

Большинство S3‑совместимых хранилищ возвращают ETag для объектов. Это можно использовать, чтобы клиенты могли эффективно проверять, изменился ли объект.

Для immutable версий это не критично (они по определению не меняются), но для алиасов latest наличие ETag полезно:

  • клиент делает запрос с If-None-Match;
  • если версия та же, S3 (или Nginx, если он кэширует) может ответить 304 Not Modified;
  • трафика уходит меньше, особенно для крупных архивов.

Важно не поломать ETag на пути от S3 до клиента. По умолчанию Nginx проксирует ETag как есть, если вы не делаете gzip или другие модификации тела ответа. Если нужно сжимать контент, разумнее делать это заранее (на этапе билда) или использовать отдельный bucket для already‑compressed контента, чтобы не потерять согласованность ETag и содержимого.

Авторизация и ограничение доступа к артефактам

Часто CI/CD артефакты не должны быть полностью публичными. Типичные кейсы:

  • билды внутренних сервисов;
  • отчеты с потенциально чувствительными данными;
  • дебажные сборки.

Чаще всего применяют одну из схем:

  • доступ по Basic Auth на уровне Nginx;
  • JWT‑авторизация (например, заголовок Authorization + Lua/njs/сторонний модуль);
  • выдача одноразовых/короткоживущих pre-signed URLs к S3 напрямую.

В контексте Nginx + S3 удобен вариант с Basic Auth или JWT: Nginx проверяет право доступа, и только потом ходит в S3. Это не влияет на Cache-Control и кэширование, но нужно помнить, что:

  • для приватных артефактов разумно ставить Cache-Control: private, если их качают разные пользователи;
  • или строить схему так, чтобы по одному URL обращался только один доверенный потребитель (например, другой сервис), и тогда можно оставлять public в Cache-Control.

Типичные грабли: что ломает кэширование артефактов

На практике проблемы с кэшем вокруг CI/CD артефактов повторяются из проекта в проект. Кратко разберем самые частые.

1. Перезапись файлов без версионирования в имени

Классика: билд кладется в myapp/prod/app.tar.gz, и на каждый релиз этот файл перезаписывается. В итоге:

  • невозможно агрессивно кэшировать файл — он же меняется;
  • клиенты периодически получают старую версию из кэша;
  • откаты и воспроизводимость билдов становятся болью.

Лечение: всегда иметь версионированный путь (с commit SHA, tag, build ID) и, при необходимости, поверх него алиасы latest/stable с коротким кэшем.

2. Смешивание HTML и ассетов без версионирования

Когда HTML и статика лежат рядом в Object Storage без раздельного Cache-Control, возможна ситуация:

  • HTML часто меняется, но кэшируется браузером (или наоборот);
  • ассеты не версионированы, и браузер «липнет» на старый JS/CSS;
  • получаются загадочные баги после релизов.

Решение: HTML хранить с коротким кэшем и без immutable, а ассеты — с версионированием в имени и длинным Cache-Control. Nginx с map по расширениям или путям помогает разделить политику.

3. «Бесконечный» кэш для latest

Иногда в азарте оптимизации включают единый Cache-Control: public, max-age=31536000, immutable для всего bucket’а на стороне S3 или Nginx. В итоге ссылки вида /latest/app.tar.gz начинают кэшироваться так же агрессивно, как и версионированные, и клиенты неделями получают старые версии.

Лечение: четко делить immutable и mutable пути и выдавать им разные заголовки.

Чек‑лист по итоговой схеме

Сведем все воедино в форме практического чек‑листа для настройки CI + S3 Object Storage + Nginx.

  • Layout ключей в S3:
    • для билдов и статики: /<project>/<env>/versions/<commit>/...;
    • для алиасов: /<project>/<env>/latest/... или /stable/...;
    • для отчетов: отдельный префикс, можно с ограниченным retention.
  • Загрузка из CI:
    • используем awscli/rclone/s5cmd;
    • гарантируем, что версионированные объекты не перезаписываются;
    • при обновлении latest/stable — либо перезаписываем объект, либо меняем ссылку.
  • Nginx как reverse‑proxy:
    • общий upstream к S3 endpoint’у;
    • отдельные location’ы для immutable и mutable путей;
    • proxy_hide_header для Cache-Control/Expires, если хотим полный контроль в Nginx.
  • Cache-Control:
    • versions: public, max-age=31536000, immutable;
    • latest/stable: пример — public, max-age=60;
    • отчеты: по ситуации, часто private, max-age=3600.
  • proxy_cache в Nginx:
    • включаем отдельные зоны для версий и алиасов;
    • настраиваем proxy_cache_valid под ваш профиль обращений;
    • добавляем X-Cache-Status для отладки.
  • Авторизация:
    • решаем, какие артефакты публичны, а какие нет;
    • для приватных — Basic Auth, JWT или pre-signed URLs;
    • корректируем Cache-Control в зависимости от модели доступа.

Заключение

Связка CI/CD → S3 Object Storage → Nginx — мощный и гибкий способ организовать хранение и раздачу артефактов, будь то билды, фронтенд‑статика или отчеты. Основные проблемы здесь не технические, а архитектурные: схема ключей, версионирование и политика кэширования.

Если заранее продумать layout объектов, разделить immutable и mutable пути, настроить Nginx на управление Cache-Control и, при необходимости, включить proxy_cache, вы получите быструю, предсказуемую и легко масштабируемую инфраструктуру для артефактов CI/CD. А дальше уже можно навешивать дополнительные плюшки — статистику скачиваний, ограничение скорости, защиту от утечек, но фундаментом останется грамотная работа с кэшем и S3.

Поделиться статьей

Вам будет интересно

Staging для WordPress на VDS: безопасные обновления без даунтайма OpenAI Статья написана AI (GPT 5)

Staging для WordPress на VDS: безопасные обновления без даунтайма

Staging-окружение для WordPress на VDS позволяет обновлять плагины, тему и ядро без риска для боевого сайта и без даунтайма. Разбе ...
Rate limiting API на Redis и Lua: простой и быстрый лимитер для микросервисов OpenAI Статья написана AI (GPT 5)

Rate limiting API на Redis и Lua: простой и быстрый лимитер для микросервисов

Разберёмся, как реализовать надёжный rate limiting для API на базе Redis и Lua. Сравним популярные алгоритмы лимитирования, спроек ...
PostgreSQL read-only реплики на VDS: быстрые отчёты без нагрузки на прод OpenAI Статья написана AI (GPT 5)

PostgreSQL read-only реплики на VDS: быстрые отчёты без нагрузки на прод

Разберём, как настроить read-only реплику PostgreSQL на VDS через streaming replication, чтобы вынести отчёты, аналитику и тяжёлые ...