AWS S3 ile Statik Web Sitesi Barındırma

Statik web sitesi barındırmak için bir sunucu kiralamak, işletim sistemi güncellemelerini takip etmek, Nginx veya Apache yapılandırmak… Bunların hepsini unutun. AWS S3, statik içeriklerinizi sunmak için son derece pratik, ölçeklenebilir ve ucuz bir çözüm sunuyor. Landing page’den portföy sitesine, dokümantasyon portalından tek sayfalık React uygulamalarına kadar pek çok senaryoda S3 statik web sitesi barındırma özelliği hayat kurtarıyor. Bu yazıda, bir S3 bucket’ını sıfırdan statik web sitesine dönüştürme sürecini tüm detaylarıyla ele alacağız.

S3 Statik Web Sitesi Barındırma Nedir?

Amazon S3, normalde nesne depolama servisi olarak kullanılır. Yani dosyaları depolar, gerektiğinde geri alırsınız. Ancak S3’ün güzel bir özelliği var: bucket’ınızı bir web sunucusu gibi davranacak şekilde yapılandırabilirsiniz. HTML, CSS, JavaScript, resim gibi statik dosyalarınızı S3’e yükleyin, web sitesi özelliğini aktif edin ve işte bu kadar. Herhangi bir sunucu yönetimi yok, patch uygulamak yok, gece yarısı disk dolu alarmları yok.

Önemli bir nokta: S3 statik barındırma sadece statik içerik için geçerlidir. PHP, Python, Node.js gibi sunucu taraflı işlemler burada çalışmaz. Eğer API çağrılarına ihtiyaç duyuyorsanız, bunları AWS Lambda veya ayrı bir backend servisine yönlendirmeniz gerekir.

Ön Koşullar

Başlamadan önce şunların hazır olması gerekiyor:

  • AWS hesabı (ücretsiz tier yeterli küçük projeler için)
  • AWS CLI kurulu ve yapılandırılmış
  • IAM kullanıcısı ile gerekli S3 izinleri
  • Barındırmak istediğiniz statik dosyalar

AWS CLI kurulumu ve yapılandırması için önce erişim anahtarlarınızı ayarlayın:

# AWS CLI yapılandırması
aws configure

# Çıktı:
# AWS Access Key ID [None]: AKIAIOSFODNN7EXAMPLE
# AWS Secret Access Key [None]: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
# Default region name [None]: eu-west-1
# Default output format [None]: json

Ben genellikle eu-west-1 (İrlanda) bölgesini kullanırım çünkü Türkiye’ye yakın ve gecikme değerleri makul seviyelerde. Siz kendi kullanım senaryonuza göre bölge seçebilirsiniz.

Bucket Oluşturma

İlk adım bir S3 bucket oluşturmak. Bucket ismi dünya genelinde benzersiz olmak zorunda, bu yüzden rastgele bir isim denemek yerine şirket adınızı veya proje adınızı önek olarak kullanın.

# Bucket oluşturma (bölge belirterek)
aws s3api create-bucket 
  --bucket benimprojem-website-2024 
  --region eu-west-1 
  --create-bucket-configuration LocationConstraint=eu-west-1

# Bucket'ın oluştuğunu doğrula
aws s3api head-bucket --bucket benimprojem-website-2024

us-east-1 dışındaki bölgeler için --create-bucket-configuration LocationConstraint parametresini mutlaka eklemeniz gerekiyor. Bu sıkça yapılan bir hata, hata mesajı da pek açıklayıcı olmadığı için başlangıçta kafayı yiyebilirsiniz.

Public Erişim Engelini Kaldırma

S3, varsayılan olarak tüm public erişimi engeller. Bu güvenlik açısından mantıklı bir varsayılan, ancak statik web sitesi için içeriklerin herkese açık olması gerekiyor. Block public access ayarlarını devre dışı bırakmamız lazım.

# Public access block ayarlarını kaldır
aws s3api put-public-access-block 
  --bucket benimprojem-website-2024 
  --public-access-block-configuration 
  "BlockPublicAcls=false,IgnorePublicAcls=false,BlockPublicPolicy=false,RestrictPublicBuckets=false"

# Ayarları doğrula
aws s3api get-public-access-block --bucket benimprojem-website-2024

Bu adımı atlamak en yaygın hatalardan biri. Bucket policy uyguladınız, web sitesi özelliğini aktif ettiniz ama hala 403 alıyorsunuz. İlk bakacağınız yer burası olsun.

Bucket Policy Oluşturma

Public erişim engelini kaldırdıktan sonra, bucket içindeki nesnelerin herkese açık okunabilir olduğunu belirten bir policy uygulamanız gerekiyor.

# Bucket policy dosyası oluştur
cat > bucket-policy.json << 'EOF'
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "PublicReadGetObject",
      "Effect": "Allow",
      "Principal": "*",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::benimprojem-website-2024/*"
    }
  ]
}
EOF

# Policy'i uygula
aws s3api put-bucket-policy 
  --bucket benimprojem-website-2024 
  --policy file://bucket-policy.json

Resource kısmındaki /* sonuna eklemeyi unutmayın. Yalnızca bucket ARN’si yazarsanız nesnelere erişim olmaz, sadece bucket listesi görünür. Bu da yaygın bir hata.

Statik Web Sitesi Özelliğini Aktifleştirme

Şimdi asıl ayarlama kısmına geliyoruz. Bucket’a hangi dosyanın ana sayfa (index), hangi dosyanın hata sayfası olduğunu söylememiz gerekiyor.

# Web sitesi yapılandırmasını uygula
aws s3api put-bucket-website 
  --bucket benimprojem-website-2024 
  --website-configuration '{
    "IndexDocument": {
      "Suffix": "index.html"
    },
    "ErrorDocument": {
      "Key": "error.html"
    }
  }'

# Yapılandırmayı kontrol et
aws s3api get-bucket-website --bucket benimprojem-website-2024

IndexDocument kısmı, kullanıcı root URL’e geldiğinde hangi dosyanın gösterileceğini belirler. ErrorDocument ise 404 ve benzeri hatalarda gösterilecek özel sayfanızı belirtir. Özel hata sayfası yazmak zahmetli görünse de kullanıcı deneyimi açısından önemli, güzel bir 404 sayfası hem arama motorları hem de kullanıcılar için faydalı.

Dosyaları Yükleme

Bucket hazır, şimdi içeriklerinizi yükleyelim. Örnek bir proje yapısı üzerinden gidelim.

# Örnek index.html oluştur
cat > index.html << 'EOF'
<!DOCTYPE html>
<html lang="tr">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Benim Projem</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <h1>Merhaba AWS S3!</h1>
    <p>Bu sayfa S3'ten sunuluyor.</p>
    <script src="app.js"></script>
</body>
</html>
EOF

# error.html oluştur
cat > error.html << 'EOF'
<!DOCTYPE html>
<html lang="tr">
<head>
    <meta charset="UTF-8">
    <title>Sayfa Bulunamadı</title>
</head>
<body>
    <h1>404 - Sayfa Bulunamadı</h1>
    <a href="/">Ana Sayfaya Dön</a>
</body>
</html>
EOF

# Tekil dosya yükle
aws s3 cp index.html s3://benimprojem-website-2024/

# Tüm dizini senkronize et (en pratik yol bu)
aws s3 sync ./dist/ s3://benimprojem-website-2024/ 
  --delete 
  --exclude "*.DS_Store" 
  --exclude ".git/*"

--delete parametresi çok önemli. Yerel klasörünüzden sildiğiniz dosyaları S3’ten de siler. Yoksa eski dosyalar bucket’ta asılı kalır ve depolama maliyetiniz gereksiz yere artar. --exclude ile de Mac’in yarattığı .DS_Store dosyaları veya git klasörü gibi web’e çıkmasını istemediğiniz dosyaları atlayabilirsiniz.

MIME Type Yönetimi

AWS CLI genellikle MIME type’ları doğru algılar, ancak bazen elle belirtmek gerekebilir. Özellikle .wasm, .webmanifest gibi yeni formatlar için:

# Belirli MIME type ile yükleme
aws s3 cp app.wasm s3://benimprojem-website-2024/ 
  --content-type "application/wasm"

# JSON manifest dosyası için
aws s3 cp manifest.webmanifest s3://benimprojem-website-2024/ 
  --content-type "application/manifest+json"

# Cache-Control header'ı ile yükleme
# HTML dosyaları için kısa cache süresi
aws s3 cp index.html s3://benimprojem-website-2024/ 
  --cache-control "max-age=300,public"

# CSS/JS için uzun cache süresi (versiyon hash kullanıyorsanız)
aws s3 sync ./dist/ s3://benimprojem-website-2024/ 
  --exclude "*.html" 
  --cache-control "max-age=31536000,public,immutable"

Cache-Control yönetimi genellikle göz ardı edilir ama performans açısından kritik. HTML dosyaları kısa süre cache’lenebilir çünkü sık değişir. Ancak main.a3f2b1c.js gibi hash’li CSS/JS dosyaları bir yıl cache’lenebilir çünkü içerik değiştiğinde dosya adı da değişecektir.

Web Sitesi URL’ini Öğrenme

Yapılandırma tamamlandıktan sonra web sitenizin URL’i şu formatta olacak:

# Web sitesi endpoint'ini öğren
echo "http://benimprojem-website-2024.s3-website-eu-west-1.amazonaws.com"

# Ya da CLI ile bucket bilgilerini görüntüle
aws s3api get-bucket-location --bucket benimprojem-website-2024

S3 web sitesi URL formatı: http://[bucket-adi].s3-website-[bölge].amazonaws.com

Dikkat edin, bu URL HTTP kullanıyor. S3’ün doğrudan web sitesi endpoint’i HTTPS desteklemiyor. Eğer HTTPS istiyorsanız (ve istemeniz gerekiyor, hem SEO hem güvenlik için) önüne CloudFront eklemek zorundasınız.

CloudFront ile HTTPS Ekleme

Gerçek dünya senaryolarında S3 web sitesini doğrudan kullanmazsınız. Önüne CloudFront koyarsınız. Hem HTTPS alırsınız, hem global CDN performansından faydalanırsınız, hem de S3 web sitesi endpoint’ini gizlemiş olursunuz.

# CloudFront distribution oluştur
aws cloudfront create-distribution 
  --distribution-config '{
    "CallerReference": "benimprojem-2024-001",
    "Origins": {
      "Quantity": 1,
      "Items": [
        {
          "Id": "S3-benimprojem-website",
          "DomainName": "benimprojem-website-2024.s3-website-eu-west-1.amazonaws.com",
          "CustomOriginConfig": {
            "HTTPPort": 80,
            "HTTPSPort": 443,
            "OriginProtocolPolicy": "http-only"
          }
        }
      ]
    },
    "DefaultCacheBehavior": {
      "ViewerProtocolPolicy": "redirect-to-https",
      "AllowedMethods": {
        "Quantity": 2,
        "Items": ["GET", "HEAD"]
      },
      "TargetOriginId": "S3-benimprojem-website",
      "ForwardedValues": {
        "QueryString": false,
        "Cookies": {"Forward": "none"}
      },
      "MinTTL": 0,
      "DefaultTTL": 86400,
      "MaxTTL": 31536000
    },
    "DefaultRootObject": "index.html",
    "Comment": "Benim Projem Web Sitesi",
    "Enabled": true,
    "PriceClass": "PriceClass_100"
  }'

PriceClass_100, yalnızca Kuzey Amerika ve Avrupa edge location’larını kullanır. Maliyeti düşürür. Global bir kitleniz varsa PriceClass_All ile tüm edge location’ları kullanabilirsiniz.

Önemli not: CloudFront origin olarak S3 bucket’ının REST API endpoint’ini değil, web sitesi endpoint’ini kullanın. Web sitesi endpoint’i kullandığınızda, CloudFront dizin isteklerinde index.html otomatik döndürülür. REST API endpoint’ini kullanırsanız alt dizinlerde sorun yaşarsınız.

Özel Domain Bağlama

Gerçek bir üretim senaryosunda Route 53 veya başka bir DNS sağlayıcısı ile kendi domain’inizi bağlamak isteyeceksiniz.

# Route 53'te hosted zone oluştur (eğer yoksa)
aws route53 create-hosted-zone 
  --name benimprojem.com 
  --caller-reference 2024-001

# Hosted zone ID'yi al
aws route53 list-hosted-zones --query 'HostedZones[?Name==`benimprojem.com.`].Id' --output text

# CloudFront domain adını öğren
aws cloudfront list-distributions 
  --query 'DistributionList.Items[0].DomainName' 
  --output text

# Route 53'e ALIAS kaydı ekle
aws route53 change-resource-record-sets 
  --hosted-zone-id Z1234567890 
  --change-batch '{
    "Changes": [
      {
        "Action": "CREATE",
        "ResourceRecordSet": {
          "Name": "benimprojem.com",
          "Type": "A",
          "AliasTarget": {
            "HostedZoneId": "Z2FDTNDATAQYW2",
            "DNSName": "d1234abcd.cloudfront.net",
            "EvaluateTargetHealth": false
          }
        }
      }
    ]
  }'

CloudFront için Route 53 ALIAS record hosted zone ID’si her zaman Z2FDTNDATAQYW2‘dir. Bu AWS’nin global CloudFront hosted zone ID’si.

CI/CD Pipeline ile Otomatik Deployment

Üretim ortamında her değişikliği elle yüklemek istemezsiniz. GitHub Actions ile otomatik deployment kurabilirsiniz.

# .github/workflows/deploy.yml içeriği
cat > deploy.yml << 'EOF'
name: Deploy to S3

on:
  push:
    branches:
      - main

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - name: Install and Build
        run: |
          npm ci
          npm run build

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: eu-west-1

      - name: Deploy to S3
        run: |
          # HTML dosyaları kısa cache
          aws s3 sync ./dist/ s3://benimprojem-website-2024/ 
            --delete 
            --exclude "*" 
            --include "*.html" 
            --cache-control "max-age=300,public"

          # Statik assetler uzun cache
          aws s3 sync ./dist/ s3://benimprojem-website-2024/ 
            --delete 
            --exclude "*.html" 
            --cache-control "max-age=31536000,public,immutable"

      - name: Invalidate CloudFront
        run: |
          aws cloudfront create-invalidation 
            --distribution-id ${{ secrets.CLOUDFRONT_DISTRIBUTION_ID }} 
            --paths "/*"
EOF

Bu pipeline, main branch’e her push yapıldığında projeyi build eder, S3’e deploy eder ve CloudFront cache’ini temizler. GitHub repository secrets kısmına AWS anahtarlarınızı ve CloudFront distribution ID’nizi eklemeyi unutmayın.

Versiyonlama ve Rollback

Bir hata yaptınız ve geri almak istiyorsunuz. S3 versiyonlama özelliğini aktif etmek bu senaryolarda hayat kurtarıcı.

# Bucket versiyonlamayı aktif et
aws s3api put-bucket-versioning 
  --bucket benimprojem-website-2024 
  --versioning-configuration Status=Enabled

# Bir dosyanın tüm versiyonlarını listele
aws s3api list-object-versions 
  --bucket benimprojem-website-2024 
  --prefix index.html

# Belirli bir versiyona geri dön
aws s3api get-object 
  --bucket benimprojem-website-2024 
  --key index.html 
  --version-id "xLgprYzBEZm8EXAMPLE" 
  index.html.backup

# Eski versiyonu aktif hale getir
aws s3 cp index.html.backup s3://benimprojem-website-2024/index.html

Versiyonlama açtığınızda her güncelleme yeni bir versiyon oluşturur ve eski dosyalar silinmez, sadece gizlenir. Bu durum zamanla depolama maliyetini artırabilir. S3 Lifecycle policy ile eski versiyonları otomatik temizleyebilirsiniz:

# Lifecycle policy ile eski versiyonları temizle
aws s3api put-bucket-lifecycle-configuration 
  --bucket benimprojem-website-2024 
  --lifecycle-configuration '{
    "Rules": [
      {
        "ID": "eski-versiyonlari-temizle",
        "Status": "Enabled",
        "NoncurrentVersionExpiration": {
          "NoncurrentDays": 30
        }
      }
    ]
  }'

Bu kural, eski versiyonları 30 gün sonra otomatik siler. Çoğu durumda 30 gün yeterli rollback penceresi sağlar.

Maliyet Optimizasyonu

S3 statik barındırma oldukça ucuz ama bazı noktalar maliyeti şişirebilir.

  • Request sayısı: S3, GET ve PUT isteklerini ücretlendirir. Yoğun trafikli sitelerde CloudFront önbelleği request sayısını önemli ölçüde azaltır.
  • Veri transferi: S3’ten internete veri aktarımı ücretlidir. CloudFront’un S3’ten veri çekmesi ise ücretsiz veya çok daha ucuzdur.
  • Gereksiz dosyalar: --delete olmadan sync yaparsanız, eski dosyalar birikir. Her eski dosya hem depolama hem de potansiyel request maliyeti demektir.
  • Versiyonlama: Versiyonlama açıkken tüm eski versiyonlar saklanır. Lifecycle policy şart.
  • Günlük loglar: Access log açtıysanız log dosyaları da depolama kaplar. Bunlar için ayrı bir bucket ve lifecycle policy kullanın.
# Bucket boyutunu ve nesne sayısını kontrol et
aws s3 ls s3://benimprojem-website-2024 --recursive --human-readable --summarize | tail -2

# Tahmini maliyet için Storage Lens kullan (hesap genelinde)
aws s3control get-storage-lens-dashboard 
  --config-id default-account-dashboard 
  --account-id 123456789012 
  --region us-east-1

Güvenlik Gerçek Dünya Notları

Birkaç önemli güvenlik noktasını paylaşmak istiyorum:

  • Hassas dosyaları yüklemeyin: .env, config.json veya API anahtarları içeren herhangi bir dosya. Public bucket demek, URL’yi bilen herkes erişebilir demek.
  • CI/CD için IAM rolü kullanın: Access key yerine mümkünse OIDC ile GitHub Actions’a IAM rolü verin. Key rotation zahmetinden kurtulursunuz.
  • Least privilege prensibini uygulayın: CI/CD pipeline’ınızın IAM kullanıcısı sadece ilgili bucket’a s3:PutObject, s3:DeleteObject, s3:GetObject ve CloudFront invalidation için gerekli izinlere sahip olsun.
  • Bucket ACL’den kaçının: Modern S3 yönetiminde ACL kullanmayın. Bucket policy ve IAM policy kullanmak daha yönetilebilir.
  • CloudFront WAF: Eğer uygulamanız saldırı hedefi olabilecek yapıdaysa, CloudFront önüne AWS WAF eklemek kaba kuvvet ve DDoS saldırılarına karşı koruma sağlar.

SPA (Single Page Application) için Özel Yapılandırma

React, Vue veya Angular gibi SPA uygulamalarında bir sorunla karşılaşırsınız: /hakkimizda gibi bir URL’ye doğrudan gidildiğinde S3, bu path’te fiziksel bir dosya olmadığı için 404 döner. Çözüm, 404 hata sayfası olarak da index.html kullanmak ve routing’i JavaScript’e bırakmaktır.

# SPA için web sitesi yapılandırması
aws s3api put-bucket-website 
  --bucket benimprojem-website-2024 
  --website-configuration '{
    "IndexDocument": {
      "Suffix": "index.html"
    },
    "ErrorDocument": {
      "Key": "index.html"
    }
  }'

Hata dökümanını index.html olarak ayarladığınızda, S3 bulamadığı her path için index.html döner ve React Router gibi client-side router’lar doğru sayfayı gösterir. Ancak bu durumda HTTP status kodu 200 yerine 404 dönecektir; SEO açısından sorun yaratabilir. CloudFront’ta custom error response yapılandırması ile 404’ü 200’e çevirebilirsiniz.

Sonuç

S3 statik web sitesi barındırma, doğru yapılandırıldığında oldukça güçlü ve ekonomik bir çözüm. Özetlemek gerekirse:

  • Bucket oluşturun, public access block’u kaldırın, bucket policy uygulayın ve web sitesi özelliğini aktifleştirin.
  • Dosyaları aws s3 sync --delete ile yükleyin, MIME type ve cache-control header’larına dikkat edin.
  • Gerçek üretim ortamı için önüne mutlaka CloudFront ekleyin. Hem HTTPS alırsınız hem CDN performansı hem de esneklik.
  • CI/CD pipeline kurarak deployment’ı otomatikleştirin.
  • Versiyonlamayı aktif edin, lifecycle policy ile maliyeti kontrol altında tutun.
  • SPA kullanıyorsanız error document’ı index.html olarak ayarlayın.

Sunucu yönetimi olmadan, patch yok, disk yok, gece alarmı yok. Web sitenizin altyapısı yerine içeriğinize odaklanabilirsiniz. Başlamak için ücretsiz tier fazlasıyla yeterli; denemek için hiçbir maliyet riskiniz yok.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir