AWS S3: хранение статики

S3 (Simple Storage Service) — объектное хранилище AWS с неограниченным объёмом и высокой доступностью (99.999999999%), используемое для статических файлов, медиа, бэкапов и хостинга SPA.

Зачем нужно

S3 дешевле и надёжнее, чем хранить файлы на сервере: не нужно думать о свободном месте на диске, файлы реплицируются в нескольких дата-центрах, доступ по HTTP. Для статических сайтов (React/Vue/Angular) S3 + CloudFront — стандартный production-паттерн. Стоимость хранения около $0.023 за GB/месяц.

Где используется

  • Хостинг статического SPA (React, Next.js static export)
  • Хранение загружаемых пользователями файлов (аватары, документы, фото)
  • Бэкапы БД и артефакты CI/CD
  • Логи и экспорты данных

Основной контент

Базовые команды AWS CLI

# Создать бакет (регион обязателен для всех кроме us-east-1)
aws s3api create-bucket \
  --bucket my-app-static \
  --region eu-west-1 \
  --create-bucket-configuration LocationConstraint=eu-west-1

# Синхронизировать папку (деплой SPA)
aws s3 sync ./dist s3://my-app-static/ \
  --delete \
  --cache-control "max-age=31536000"

# index.html — без кэша (всегда свежий)
aws s3 cp ./dist/index.html s3://my-app-static/ \
  --cache-control "no-cache"

# Список файлов
aws s3 ls s3://my-app-static/ --recursive --human-readable

Хостинг SPA на S3

# Включить статический хостинг
aws s3api put-bucket-website \
  --bucket my-app-static \
  --website-configuration '{
    "IndexDocument": {"Suffix": "index.html"},
    "ErrorDocument": {"Key": "index.html"}
  }'

# Политика публичного доступа
aws s3api put-bucket-policy \
  --bucket my-app-static \
  --policy '{
    "Version": "2012-10-17",
    "Statement": [{
      "Effect": "Allow",
      "Principal": "*",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::my-app-static/*"
    }]
  }'

Работа с S3 из Node.js (AWS SDK v3)

npm install @aws-sdk/client-s3 @aws-sdk/s3-request-presigner
const { S3Client, PutObjectCommand, GetObjectCommand } = require('@aws-sdk/client-s3');
const { getSignedUrl } = require('@aws-sdk/s3-request-presigner');

const s3 = new S3Client({ region: 'eu-west-1' });

// Загрузить файл
async function uploadFile(buffer, key, contentType) {
  await s3.send(new PutObjectCommand({
    Bucket: process.env.S3_BUCKET,
    Key: key,
    Body: buffer,
    ContentType: contentType,
  }));
}

// Presigned URL (временный доступ к приватному файлу)
async function getPresignedUrl(key) {
  const command = new GetObjectCommand({
    Bucket: process.env.S3_BUCKET,
    Key: key,
  });
  return getSignedUrl(s3, command, { expiresIn: 3600 }); // 1 час
}

Деплой SPA в GitHub Actions

- name: Deploy to S3
  run: |
    aws s3 sync ./dist s3://${{ secrets.S3_BUCKET }}/ --delete
    aws cloudfront create-invalidation \
      --distribution-id ${{ secrets.CF_DISTRIBUTION_ID }} \
      --paths "/*"
  env:
    AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
    AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
    AWS_REGION: eu-west-1

Частые ошибки

  • Делать весь бакет публичным для хранения приватных файлов — использовать presigned URL
  • Не настраивать Cache-Control — браузер кэширует старые файлы после деплоя
  • Не включить versioning для бэкапов — случайно удалённый файл невозможно восстановить
  • Хардкодить AWS Access Key в коде — использовать IAM Role или GitHub Secrets

Связанные темы

Ресурсы