Scaleway provides an object storage service compatible with the S3 API.

Here’s how to configure your bucket via Terraform, though you can also do it directly through the console.

Table of contents:

  1. IAM Permissions Management
  2. Bucket Creation
  3. Ruby on Rails Configuration

Note: the var.scw_project variable contains your project UUID, which you can find in the console.

IAM Permissions Management

First, we’ll create an application to which we’ll assign permissions and from which we’ll retrieve the key pair for authentication.

Here, I’m granting full permissions (ObjectStorageFullAccess) for the Object Storage API, but you can be more restrictive if you prefer.

# iam.tf

resource "scaleway_iam_application" "my_app" {
  name        = "MyApp"
  description = "MyApp application"
}

resource "scaleway_iam_api_key" "my_app" {
  application_id     = scaleway_iam_application.my_app.id
  description        = "MyApp API Key"
  default_project_id = var.scw_project
}

resource "scaleway_iam_policy" "my_app_object_storage" {
  name           = "MyApp Object Storage"
  description    = "Gives MyApp full access to object storage"
  application_id = scaleway_iam_application.my_app.id

  rule {
    project_ids          = [var.scw_project]
    permission_set_names = ["ObjectStorageFullAccess"]
  }
}

Add the outputs below to retrieve the authentication key pair:

# outputs.tf

output "my_app_access_key" {
  value     = scaleway_iam_api_key.my_app.access_key
  sensitive = true
}

output "my_app_secret_key" {
  value     = scaleway_iam_api_key.my_app.secret_key
  sensitive = true
}

Bucket creation

Let’s move on to creating the bucket. I’ve added rules to migrate objects between storage classes, but you’re not required to use them. CORS rules are important if you want to use direct uploads.

# object_storage.tf

resource "scaleway_object_bucket" "my_app" {
  name       = "my_app"
  region     = "fr-par"
  project_id = var.scw_project

  tags = {
    "Application" = "MyApp"
  }

  lifecycle_rule {
    id                                     = "abort_incomplete_multipart_upload"
    enabled                                = true
    abort_incomplete_multipart_upload_days = 7
  }

  lifecycle_rule {
    id      = "onezone_ia"
    enabled = true

    transition {
      days          = 30
      storage_class = "ONEZONE_IA"
    }
  }

  lifecycle_rule {
    id      = "glacier"
    enabled = true

    transition {
      days          = 120
      storage_class = "GLACIER"
    }
  }

  cors_rule {
    allowed_methods = ["GET", "HEAD", "POST", "PUT", "DELETE"]
    allowed_headers = ["*"]
    allowed_origins = [
      "*"
    ]
    expose_headers  = ["ETag"]
    max_age_seconds = 3600
  }
}

The last thing we need to configure here is the bucket policy to ensure that the application can access it. Note that I’ve also added a rule for my own user so I can still access the bucket from the console.

# object_storage.tf

resource "scaleway_object_bucket_policy" "my_app" {
  bucket = scaleway_object_bucket.my_app.name

  policy = jsonencode({
    Id      = "my_app",
    Version = "2023-04-17",
    Statement = [
      {
        Effect    = "Allow"
        Action    = ["*"]
        Principal = { SCW = "application_id:${scaleway_iam_application.my_app.id}" }
        Resource = [
          scaleway_object_bucket.my_app.name,
          "${scaleway_object_bucket.my_app.name}/*",
        ]
      },
      {
        Effect    = "Allow"
        Action    = ["*"]
        Principal = { SCW = "user_id:${var.scw_my_user}" }
        Resource = [
          scaleway_object_bucket.my_app.name,
          "${scaleway_object_bucket.my_app.name}/*",
        ]
      }
    ]
  })
}

Ruby on Rails Configuration

We can now configure Active Storage with our setup.

# config/storage.yml

scaleway:
  service: S3
  access_key_id: <%= Rails.application.credentials.dig(:scaleway, :access_key) %>
  secret_access_key: <%= Rails.application.credentials.dig(:scaleway, :secret_key) %>
  region: fr-par
  bucket: my_app
  endpoint: https://s3.fr-par.scw.cloud

Retrieve the key pair using the commands below:

tf output my_app_access_key
tf output my_app_secret_key

And that’s it!