> ## Documentation Index
> Fetch the complete documentation index at: https://spreecommerce.org/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Amazon Web Services (AWS)

> Learn how to deploy your Spree application on Amazon Web Services (AWS).

Amazon Web Services offers reliable, scalable, and inexpensive cloud computing services. AWS is also one of the most popular choices for hosting a Spree application.

We recommend using AWS ECS Fargate to host your Spree application via [Docker image](/developer/deployment/docker).

## Required AWS Services

To fully run your Spree application on AWS, you will need the following services:

| Service                                                                | Description                                                                                                                                                                                                                                                                                                                  |
| ---------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [AWS ECS Fargate](https://aws.amazon.com/fargate/)                     | Fully managed container orchestration — run and scale containers without managing infrastructure.                                                                                                                                                                                                                            |
| [AWS RDS](https://aws.amazon.com/rds/)                                 | Managed relational database. Spree works with [Aurora PostgreSQL](https://aws.amazon.com/rds/aurora/), [Aurora MySQL](https://aws.amazon.com/rds/aurora/), [RDS PostgreSQL](https://aws.amazon.com/rds/postgresql/), [RDS MySQL](https://aws.amazon.com/rds/mysql/), and [RDS MariaDB](https://aws.amazon.com/rds/mariadb/). |
| [AWS ElastiCache](https://aws.amazon.com/elasticache/)                 | Valkey or Redis for background jobs (Sidekiq), [caching](/developer/deployment/caching), and Action Cable. We recommend separate instances for jobs and cache.                                                                                                                                                               |
| [AWS S3](https://aws.amazon.com/s3/)                                   | Object storage for uploaded files (product images, etc.). [More information](/developer/deployment/assets#aws-s3).                                                                                                                                                                                                           |
| [AWS CloudFront](https://aws.amazon.com/cloudfront/)                   | CDN for asset delivery (images, stylesheets, JavaScript).                                                                                                                                                                                                                                                                    |
| [AWS Route 53](https://aws.amazon.com/route53/)                        | DNS service for domain name management.                                                                                                                                                                                                                                                                                      |
| [AWS Certificate Manager](https://aws.amazon.com/certificate-manager/) | Free SSL/TLS certificates. Spree requires HTTPS in production.                                                                                                                                                                                                                                                               |
| [AWS ECR](https://aws.amazon.com/ecr/)                                 | Docker container registry for storing your application images.                                                                                                                                                                                                                                                               |

## Docker Image

You can use the [official Docker image](/developer/deployment/docker) (`ghcr.io/spree/spree`) directly, or build your own from your Rails application's Dockerfile.

To build and deploy a custom image to AWS ECR via GitHub Actions:

```yaml theme={"theme":"night-owl"}
name: Deploy to AWS Fargate

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

env:
  AWS_REGION: us-east-1
  ECR_REPOSITORY: spree-starter
  ECS_SERVICE_WEB: spree-web
  ECS_SERVICE_WORKER: spree-worker
  ECS_CLUSTER: spree-cluster
  
jobs:
  build:
    name: Build and Push to ECR
    runs-on: ubuntu-latest
    
    outputs:
      image: ${{ steps.build-image.outputs.image }}
      image-tag: ${{ steps.build-image.outputs.image-tag }}
    
    steps:
    - name: Checkout code
      uses: actions/checkout@v4
    
    - 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: ${{ env.AWS_REGION }}
    
    - name: Login to Amazon ECR
      id: login-ecr
      uses: aws-actions/amazon-ecr-login@v2
    
    - name: Build, tag, and push image to Amazon ECR
      id: build-image
      env:
        ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
        IMAGE_TAG: ${{ github.sha }}
      run: |
        # Build a docker container and push it to ECR
        docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
        docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
        echo "image=$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" >> $GITHUB_OUTPUT
        echo "image-tag=$IMAGE_TAG" >> $GITHUB_OUTPUT
  
  deploy-web:
    name: Deploy Web Service
    runs-on: ubuntu-latest
    needs: build
    if: github.ref == 'refs/heads/main'
    
    steps:
    - name: Checkout code
      uses: actions/checkout@v4
    
    - 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: ${{ env.AWS_REGION }}
    
    - name: Fill in the new image ID in the Amazon ECS task definition
      id: task-def-web
      uses: aws-actions/amazon-ecs-render-task-definition@v1
      env:
        ECR_REGISTRY: ${{ needs.build.outputs.image }}
        IMAGE_TAG: ${{ needs.build.outputs.image-tag }}
        AWS_ACCOUNT_ID: ${{ secrets.AWS_ACCOUNT_ID }}
      with:
        task-definition: .aws/web-task-definition.json
        container-name: web
        image: ${{ needs.build.outputs.image }}
    
    - name: Deploy Amazon ECS task definition for web
      uses: aws-actions/amazon-ecs-deploy-task-definition@v1
      with:
        task-definition: ${{ steps.task-def-web.outputs.task-definition }}
        service: ${{ env.ECS_SERVICE_WEB }}
        cluster: ${{ env.ECS_CLUSTER }}
        wait-for-service-stability: true
  
  deploy-worker:
    name: Deploy Worker Service
    runs-on: ubuntu-latest
    needs: build
    if: github.ref == 'refs/heads/main'
    
    steps:
    - name: Checkout code
      uses: actions/checkout@v4
    
    - 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: ${{ env.AWS_REGION }}
    
    - name: Fill in the new image ID in the Amazon ECS task definition
      id: task-def-worker
      uses: aws-actions/amazon-ecs-render-task-definition@v1
      env:
        ECR_REGISTRY: ${{ needs.build.outputs.image }}
        IMAGE_TAG: ${{ needs.build.outputs.image-tag }}
        AWS_ACCOUNT_ID: ${{ secrets.AWS_ACCOUNT_ID }}
      with:
        task-definition: .aws/worker-task-definition.json
        container-name: worker
        image: ${{ needs.build.outputs.image }}
    
    - name: Deploy Amazon ECS task definition for worker
      uses: aws-actions/amazon-ecs-deploy-task-definition@v1
      with:
        task-definition: ${{ steps.task-def-worker.outputs.task-definition }}
        service: ${{ env.ECS_SERVICE_WORKER }}
        cluster: ${{ env.ECS_CLUSTER }}
        wait-for-service-stability: true

  migrate:
    name: Run Database Migrations
    runs-on: ubuntu-latest
    needs: [build, deploy-web]
    if: github.ref == 'refs/heads/main'
    
    steps:
    - name: Checkout code
      uses: actions/checkout@v4
    
    - 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: ${{ env.AWS_REGION }}
    
    - name: Run database migrations
      run: |
        aws ecs run-task \
          --cluster ${{ env.ECS_CLUSTER }} \
          --task-definition spree-web \
          --overrides '{
            "containerOverrides": [{
              "name": "web",
              "command": ["bundle", "exec", "rails", "db:migrate"]
            }]
          }' \
          --launch-type FARGATE \
          --network-configuration '{
            "awsvpcConfiguration": {
              "subnets": ["'${{ secrets.SUBNET_ID_1 }}'", "'${{ secrets.SUBNET_ID_2 }}'"],
              "securityGroups": ["'${{ secrets.SECURITY_GROUP_ID }}'"],
              "assignPublicIp": "ENABLED"
            }
          }'
```

This action requires secrets to be set in your GitHub repository. You can find the full list of secrets in the [AWS ECS Deploy Task Definition](https://github.com/aws-actions/amazon-ecs-deploy-task-definition) GitHub Actions repository.

| Secret                  | Description           |
| ----------------------- | --------------------- |
| `AWS_ACCESS_KEY_ID`     | AWS access key ID     |
| `AWS_SECRET_ACCESS_KEY` | AWS secret access key |
| `AWS_ACCOUNT_ID`        | AWS account ID        |
| `SUBNET_ID_1`           | First subnet ID       |
| `SUBNET_ID_2`           | Second subnet ID      |
| `SECURITY_GROUP_ID`     | Security group ID     |

## Environment Variables

Store secrets in [AWS Secrets Manager](https://aws.amazon.com/secrets-manager/) and reference them in your task definitions. Non-sensitive configuration goes in the `environment` array directly.

For a full list of available variables, see [Environment Variables](/developer/deployment/environment_variables).

### Secrets Manager

Create the following secrets in AWS Secrets Manager:

| Secret Name             | Variable          | Description                                           |
| ----------------------- | ----------------- | ----------------------------------------------------- |
| `spree/database-url`    | `DATABASE_URL`    | PostgreSQL connection URL                             |
| `spree/redis-url`       | `REDIS_URL`       | Redis URL for background jobs and Action Cable        |
| `spree/redis-cache-url` | `REDIS_CACHE_URL` | Redis URL for caching (separate instance recommended) |
| `spree/secret-key-base` | `SECRET_KEY_BASE` | Generate with `bin/rails secret`                      |

Optional secrets for email delivery, file storage, and error tracking:

| Secret Name           | Variable        | Description                   |
| --------------------- | --------------- | ----------------------------- |
| `spree/smtp-password` | `SMTP_PASSWORD` | SMTP auth password            |
| `spree/sentry-dsn`    | `SENTRY_DSN`    | Sentry DSN for error tracking |

<Tip>
  S3 file storage credentials are not needed as environment variables when your ECS task role has the appropriate S3 permissions. Use IAM roles instead of access keys when possible.
</Tip>

## ECS Task Definitions

You will need two ECS task definitions: one for the web service and one for the worker service. Save these as `.aws/web-task-definition.json` and `.aws/worker-task-definition.json` in your repository.

### Web Service

```json theme={"theme":"night-owl"}
{
  "family": "spree-web",
  "networkMode": "awsvpc",
  "requiresCompatibilities": ["FARGATE"],
  "cpu": "1024",
  "memory": "4096",
  "executionRoleArn": "arn:aws:iam::${AWS_ACCOUNT_ID}:role/ecsTaskExecutionRole",
  "taskRoleArn": "arn:aws:iam::${AWS_ACCOUNT_ID}:role/ecsTaskRole",
  "containerDefinitions": [
    {
      "name": "web",
      "image": "${ECR_REGISTRY}/${ECR_REPOSITORY}:${IMAGE_TAG}",
      "portMappings": [
        {
          "containerPort": 3000,
          "protocol": "tcp"
        }
      ],
      "essential": true,
      "environment": [
        {
          "name": "RAILS_ENV",
          "value": "production"
        },
        {
          "name": "PORT",
          "value": "3000"
        },
        {
          "name": "RAILS_MAX_THREADS",
          "value": "3"
        },
        {
          "name": "WEB_CONCURRENCY",
          "value": "auto"
        },
        {
          "name": "RAILS_LOG_LEVEL",
          "value": "info"
        },
        {
          "name": "AWS_BUCKET",
          "value": "your-spree-bucket"
        }
      ],
      "secrets": [
        {
          "name": "DATABASE_URL",
          "valueFrom": "arn:aws:secretsmanager:${AWS_REGION}:${AWS_ACCOUNT_ID}:secret:spree/database-url"
        },
        {
          "name": "REDIS_URL",
          "valueFrom": "arn:aws:secretsmanager:${AWS_REGION}:${AWS_ACCOUNT_ID}:secret:spree/redis-url"
        },
        {
          "name": "REDIS_CACHE_URL",
          "valueFrom": "arn:aws:secretsmanager:${AWS_REGION}:${AWS_ACCOUNT_ID}:secret:spree/redis-cache-url"
        },
        {
          "name": "SECRET_KEY_BASE",
          "valueFrom": "arn:aws:secretsmanager:${AWS_REGION}:${AWS_ACCOUNT_ID}:secret:spree/secret-key-base"
        }
      ],
      "logConfiguration": {
        "logDriver": "awslogs",
        "options": {
          "awslogs-group": "/ecs/spree-web",
          "awslogs-region": "${AWS_REGION}",
          "awslogs-stream-prefix": "ecs"
        }
      },
      "healthCheck": {
        "command": ["CMD-SHELL", "curl -f http://localhost:3000/up || exit 1"],
        "interval": 30,
        "timeout": 5,
        "retries": 3,
        "startPeriod": 60
      }
    }
  ]
}
```

### Worker Service

```json theme={"theme":"night-owl"}
{
  "family": "spree-worker",
  "networkMode": "awsvpc",
  "requiresCompatibilities": ["FARGATE"],
  "cpu": "512",
  "memory": "2048",
  "executionRoleArn": "arn:aws:iam::${AWS_ACCOUNT_ID}:role/ecsTaskExecutionRole",
  "taskRoleArn": "arn:aws:iam::${AWS_ACCOUNT_ID}:role/ecsTaskRole",
  "containerDefinitions": [
    {
      "name": "worker",
      "image": "${ECR_REGISTRY}/${ECR_REPOSITORY}:${IMAGE_TAG}",
      "command": ["bundle", "exec", "sidekiq"],
      "essential": true,
      "environment": [
        {
          "name": "RAILS_ENV",
          "value": "production"
        },
        {
          "name": "RAILS_LOG_LEVEL",
          "value": "info"
        }
      ],
      "secrets": [
        {
          "name": "DATABASE_URL",
          "valueFrom": "arn:aws:secretsmanager:${AWS_REGION}:${AWS_ACCOUNT_ID}:secret:spree/database-url"
        },
        {
          "name": "REDIS_URL",
          "valueFrom": "arn:aws:secretsmanager:${AWS_REGION}:${AWS_ACCOUNT_ID}:secret:spree/redis-url"
        },
        {
          "name": "SECRET_KEY_BASE",
          "valueFrom": "arn:aws:secretsmanager:${AWS_REGION}:${AWS_ACCOUNT_ID}:secret:spree/secret-key-base"
        }
      ],
      "logConfiguration": {
        "logDriver": "awslogs",
        "options": {
          "awslogs-group": "/ecs/spree-worker",
          "awslogs-region": "${AWS_REGION}",
          "awslogs-stream-prefix": "ecs"
        }
      },
      "healthCheck": {
        "command": ["CMD-SHELL", "pgrep -f sidekiq || exit 1"],
        "interval": 30,
        "timeout": 5,
        "retries": 3,
        "startPeriod": 60
      }
    }
  ]
}
```

## Production Sizing

The task definitions above are sized for production. Here is a summary and scaling guidance:

| Service                 | Fargate CPU | Fargate Memory                 | Instances                         |
| ----------------------- | ----------- | ------------------------------ | --------------------------------- |
| **Web**                 | 1 vCPU      | 4 GB                           | 2+ (use ECS Service Auto Scaling) |
| **Worker**              | 0.5 vCPU    | 2 GB                           | 1+                                |
| **RDS (PostgreSQL)**    | —           | `db.r6g.large` (2 vCPU, 16 GB) | 1 primary + read replica          |
| **ElastiCache (jobs)**  | —           | `cache.r6g.large` (13 GB)      | 1                                 |
| **ElastiCache (cache)** | —           | `cache.r6g.large` (13 GB)      | 1                                 |

### Auto Scaling

Use [ECS Service Auto Scaling](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/service-auto-scaling.html) to scale the web service based on CPU or memory utilization:

```bash theme={"theme":"night-owl"}
# Register a scalable target (min 2, max 6 tasks)
aws application-autoscaling register-scalable-target \
  --service-namespace ecs \
  --resource-id service/spree-cluster/spree-web \
  --scalable-dimension ecs:service:DesiredCount \
  --min-capacity 2 \
  --max-capacity 6

# Scale based on average CPU utilization (target 70%)
aws application-autoscaling put-scaling-policy \
  --service-namespace ecs \
  --resource-id service/spree-cluster/spree-web \
  --scalable-dimension ecs:service:DesiredCount \
  --policy-name spree-web-cpu-scaling \
  --policy-type TargetTrackingScaling \
  --target-tracking-scaling-policy-configuration '{
    "TargetValue": 70.0,
    "PredefinedMetricSpecification": {
      "PredefinedMetricType": "ECSServiceAverageCPUUtilization"
    },
    "ScaleInCooldown": 300,
    "ScaleOutCooldown": 60
  }'
```

## After Deployment

### Admin Dashboard

Access your admin panel at:

```
https://<your-domain>/admin
```

Default credentials are created during `db:seed`. Change them immediately after first login.

### Database Migrations

The GitHub Actions workflow above runs migrations automatically on deploy. To run migrations manually:

```bash theme={"theme":"night-owl"}
aws ecs run-task \
  --cluster spree-cluster \
  --task-definition spree-web \
  --overrides '{
    "containerOverrides": [{
      "name": "web",
      "command": ["bundle", "exec", "rails", "db:migrate"]
    }]
  }' \
  --launch-type FARGATE \
  --network-configuration '{
    "awsvpcConfiguration": {
      "subnets": ["subnet-xxx", "subnet-yyy"],
      "securityGroups": ["sg-xxx"],
      "assignPublicIp": "DISABLED"
    }
  }'
```

## Next Steps

* [Asset Storage](/developer/deployment/assets) — configure S3 for product images and uploads
* [Configure CDN](/developer/deployment/cdn) — set up CloudFront for asset delivery
* [Configure caching](/developer/deployment/caching) — use ElastiCache for application caching
* [Set environment variables](/developer/deployment/environment_variables) — SMTP, Sentry, SSL, etc.
