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
Связанные темы
- _MOC DevOps
- AWS -- обзор сервисов для веб-разработчика
- AWS Lambda -- serverless
- Cloudflare -- CDN и защита