Examples

Simple leader-follower setup

Using the CLI

In this example, we will do a simple configuration with one leader bucket, replicating into the follower bucket. Writes in the leader will be visible inside of the follower.

  1. Using the following IAM configuration, create an IAM role for the replication:
# policy.json
{
  "default-service-strategy": "deny",
  "services": {
    "sos": {
      "type": "rules",
      "rules": [
        {
          "action": "allow",
          "expression": "parameters.bucket == 'my-source' && operation == 'get-object'"
        },
        {
          "action": "allow",
          "expression": "parameters.bucket == 'my-destination' && operation.startsWith('put-object')"
        }
      ]
    }
  }
}
$ cat policy.json | exo iam role create --editable=true --description 'sos bucket replication between my-source and my-destination' --policy - replication-my-source-my-destination
┼─────────────┼─────────────────────────────────────────────────────────────┼
│ ID          │ 6f00a21e-a535-4286-86c5-e81eeab3b4ff                        │
│ Name        │ replication-my-source-my-destination                        │
│ Description │ sos bucket replication between my-source and my-destination │
│ Editable    │ true│ Labels      │ n/a                                                         │
│ Permissions │ n/a                                                         │
┼─────────────┼─────────────────────────────────────────────────────────────┼
  1. Create the source and destination buckets, with the correct configuration
$ exo storage create --zone ch-dk-2 my-source
┼──────────────────┼────────────────────────────────────────────┼
│     STORAGE      │                                            │
┼──────────────────┼────────────────────────────────────────────┼
│ Name             │ my-source                                  │
│ Zone             │ ch-dk-2                                    │
│ ACL              │                                            │
│                  │   Read           -                         │
│                  │   Write          -                         │
│                  │   Read ACP       -                         │
│                  │   Write ACP      -                         │
│                  │   Full Control   xxxxxxxxxxxxxxxxxxxxxxx   │
│                  │                                            │
│ CORS             │                                            │
│ Object Ownership │ BucketOwnerEnforced                        │
┼──────────────────┼────────────────────────────────────────────┼
$ exo storage create --zone at-vie-1 my-destination
┼──────────────────┼────────────────────────────────────────────┼
│     STORAGE      │                                            │
┼──────────────────┼────────────────────────────────────────────┼
│ Name             │ my-destination                             │
│ Zone             │ at-vie-1                                   │
│ ACL              │                                            │
│                  │   Read           -                         │
│                  │   Write          -                         │
│                  │   Read ACP       -                         │
│                  │   Write ACP      -                         │
│                  │   Full Control   xxxxxxxxxxxxxxxxxxxxxxx   │
│                  │                                            │
│ CORS             │                                            │
│ Object Ownership │ BucketOwnerEnforced                        │
┼──────────────────┼────────────────────────────────────────────┼

$ exo storage bucket versioning enable --zone ch-dk-2 my-source
$ exo storage bucket versioning enable --zone at-vie-1 my-destination
  1. Using the following replication configuration, enable the replication between the two buckets.
# config.json
{
    "Role": "6f00a21e-a535-4286-86c5-e81eeab3b4ff",
    "Rules": [{
        "ID": "vie1-follower",
        "Priority": 1,
        "Filter": {
            "Prefix": ""
        },
        "Status": "Enabled",
        "DeleteMarkerReplication": {
            "Status": "Enabled"
        },
        "Destination": {
            "Bucket": "my-destination"
        }
    }]
}
$ exo storage bucket replication set --zone ch-dk-2 sos://my-source ./config.json
  1. Confirm it works as expected:
$ exo storage upload -r ./ sos://my-source/
config.json      [===========================================================================] 372.00 b / 372.00 b | 0s
policy.json      [===========================================================================] 418.00 b / 418.00 b | 0s
$ exo storage list sos://my-source/
2025-02-20 15:50:12 UTC  372 B  config.json
2025-02-20 15:50:13 UTC  418 B  policy.json
$ exo storage list sos://my-destination/
2025-02-20 15:50:12 UTC  372 B  config.json
2025-02-20 15:50:13 UTC  418 B  policy.json

# You can also check the Replication Status of each object individually
$ exo storage show sos://my-source/config.json
[...]
│ Path               │ config.json │
│ Bucket             │ my-source   │
│ Replication Status │ COMPLETED   │
$ exo storage show sos://my-destination/config.json
[...]
│ Path               │ config.json    │
│ Bucket             │ my-destination │
│ Replication Status │ REPLICA        │

Using the portal

  1. In IAM -> Roles -> Add, create a role with the following configuration:
    1. name: replication-my-source-my-destination
    2. description: sos bucket replication between my-source and my-destination
    3. Editable Policy: true
    4. Policy: Using the Advanced mode, submit the following configuration:
{
  "default-service-strategy": "deny",
  "services": {
    "sos": {
      "type": "rules",
      "rules": [
        {
          "action": "allow",
          "expression": "parameters.bucket == 'my-source' && operation == 'get-object'"
        },
        {
          "action": "allow",
          "expression": "parameters.bucket == 'my-destination' && operation.startsWith('put-object')"
        }
      ]
    }
  }
}

Portal: Creating role

  1. In Storage -> Add, create the two buckets.
    1. In the list of buckets, for your two bucket, click on ... and open the details page. Enable Versioning for both buckets.
    2. For our source bucket, open the Replication tab and write the following configuration:
{
    "Role": "6f00a21e-a535-4286-86c5-e81eeab3b4ff",
    "Rules": [{
        "ID": "vie1-follower",
        "Priority": 1,
        "Filter": {
            "Prefix": ""
        },
        "Status": "Enabled",
        "DeleteMarkerReplication": {
            "Status": "Enabled"
        },
        "Destination": {
            "Bucket": "my-destination"
        }
    }]
}

Portal: Configuring replication

  1. Finally, we can upload objects in our source bucket, and confirm they eventually appear in the destination bucket.

Portal: Validating the replication

Bi-directional replication

You can also synchronize two buckets in both direction. This can be used by example for creating an eventually-consistent multi-active system, or to ensure easy failover and rollback of your application in case of datacenter faults. Writes to any of the two buckets will be eventually replicated to the other.

Writes are only replicated once, this will not create any infinite loops in the replication process.

  1. Using the following IAM configuration, create an IAM role for the replication:
# a-b.json
{
  "default-service-strategy": "deny",
  "services": {
    "sos": {
      "type": "rules",
      "rules": [
        {
          "action": "allow",
          "expression": "parameters.bucket == 'bucket-a' && operation == 'get-object'"
        },
        {
          "action": "allow",
          "expression": "parameters.bucket == 'bucket-b' && operation.startsWith('put-object')"
        }
      ]
    }
  }
}
# b-a.json
{
  "default-service-strategy": "deny",
  "services": {
    "sos": {
      "type": "rules",
      "rules": [
        {
          "action": "allow",
          "expression": "parameters.bucket == 'bucket-b' && operation == 'get-object'"
        },
        {
          "action": "allow",
          "expression": "parameters.bucket == 'bucket-a' && operation.startsWith('put-object')"
        }
      ]
    }
  }
}
$ cat a-b.json | exo iam role create --editable=true --description 'sos bucket replication between bucket-a and bucket-b' --policy - replication-bucket-a-bucket-b
┼─────────────┼──────────────────────────────────────────────────────┼
│ ID          │ 91390068-c386-4d9d-b0f1-951e2baa818e                 │
│ Name        │ replication-bucket-a-bucket-b                        │
│ Description │ sos bucket replication between bucket-a and bucket-b │
│ Editable    │ true│ Labels      │ n/a                                                  │
│ Permissions │ n/a                                                  │
┼─────────────┼──────────────────────────────────────────────────────┼
$ cat b-a.json | exo iam role create --editable=true --description 'sos bucket replication between bucket-b and bucket-a' --policy - replication-bucket-b-bucket-a
┼─────────────┼──────────────────────────────────────────────────────┼
│ ID          │ e23c886b-8091-4adc-b567-7bf796bed37b                 │
│ Name        │ replication-bucket-b-bucket-a                        │
│ Description │ sos bucket replication between bucket-b and bucket-a │
│ Editable    │ true│ Labels      │ n/a                                                  │
│ Permissions │ n/a                                                  │
┼─────────────┼──────────────────────────────────────────────────────┼
  1. Create the A and B buckets, with the correct configuration
$ exo storage create --zone ch-dk-2 bucket-a
[...]
$ exo storage create --zone at-vie-1 bucket-b
[...]

$ exo storage bucket versioning enable --zone ch-dk-2 bucket-a
$ exo storage bucket versioning enable --zone at-vie-1 bucket-b
  1. Using the following replication configuration, enable the replication between the two buckets.
# conf-a.json
{
    "Role": "91390068-c386-4d9d-b0f1-951e2baa818e",
    "Rules": [{
        "ID": "to-bucket-b",
        "Priority": 1,
        "Filter": {
            "Prefix": ""
        },
        "Status": "Enabled",
        "DeleteMarkerReplication": {
            "Status": "Enabled"
        },
        "Destination": {
            "Bucket": "bucket-b"
        }
    }]
}
# conf-b.json
{
    "Role": "e23c886b-8091-4adc-b567-7bf796bed37b",
    "Rules": [{
        "ID": "to-bucket-a",
        "Priority": 1,
        "Filter": {
            "Prefix": ""
        },
        "Status": "Enabled",
        "DeleteMarkerReplication": {
            "Status": "Enabled"
        },
        "Destination": {
            "Bucket": "bucket-a"
        }
    }]
}
$ exo storage bucket replication set --zone ch-dk-2 sos://bucket-a ./conf-a.json
$ exo storage bucket replication set --zone at-vie-1 sos://bucket-b ./conf-b.json
  1. Confirm it works as expected:
$> exo storage upload *a.json sos://bucket-a
b-a.json         [==============================================================================] 410.00 b / 410.00 b | 0s
conf-a.json      [==============================================================================] 364.00 b / 364.00 b | 0s
$> exo storage upload *b.json sos://bucket-b
a-b.json         [==============================================================================] 410.00 b / 410.00 b | 0s
conf-b.json      [==============================================================================] 364.00 b / 364.00 b | 0s

$ exo storage list sos://bucket-a
2025-02-24 14:06:34 UTC  410 B  a-b.json
2025-02-24 14:06:28 UTC  410 B  b-a.json
2025-02-24 14:06:28 UTC  364 B  conf-a.json
2025-02-24 14:06:34 UTC  364 B  conf-b.json
$ exo storage list sos://bucket-b
2025-02-24 14:06:34 UTC  410 B  a-b.json
2025-02-24 14:06:28 UTC  410 B  b-a.json
2025-02-24 14:06:28 UTC  364 B  conf-a.json
2025-02-24 14:06:34 UTC  364 B  conf-b.json