synced-folder logo
Synced Folder v0.10.2, Feb 16 23

Synced Folder

A Pulumi component that synchronizes the contents of a local folder to any Amazon S3 bucket, Azure Blob Storage container, or Google Cloud Storage bucket. Use this component to publish content easily to your cloud provider of choice.

Usage

Sync to Amazon S3

import * as aws from "@pulumi/aws";
import * as synced from "@pulumi/synced-folder";

const bucket = new aws.s3.Bucket("my-bucket", {
    acl: aws.s3.PublicReadAcl,
});

const folder = new synced.S3BucketFolder("synced-folder", {
    path: "./my-folder",
    bucketName: bucket.bucket,
    acl: aws.s3.PublicReadAcl,
});
from pulumi_aws import s3
import pulumi_synced_folder

bucket = s3.Bucket(
    "my-bucket",
    acl=s3.CannedAcl.PUBLIC_READ,
)

folder = pulumi_synced_folder.S3BucketFolder(
    "synced-folder",
    path="./my-folder",
    bucket_name=bucket.bucket,
    acl=s3.CannedAcl.PUBLIC_READ,
)
package main

import (
	"github.com/pulumi/pulumi-aws/sdk/v5/go/aws/s3"
	synced "github.com/pulumi/pulumi-synced-folder/sdk/go/synced-folder"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {

		bucket, err := s3.NewBucket(ctx, "my-bucket", &s3.BucketArgs{
			Acl: s3.CannedAclPublicRead,
		})
		if err != nil {
			return err
		}

		_, err = synced.NewS3BucketFolder(ctx, "synced-folder", &synced.S3BucketFolderArgs{
			Path:       pulumi.String("./my-folder"),
			BucketName: bucket.Bucket,
			Acl:        s3.CannedAclPublicRead,
		})
		if err != nil {
			return err
		}

		return nil
	})
}
using Pulumi;
using Pulumi.Aws.S3;
using Pulumi.SyncedFolder;

return await Deployment.RunAsync(() =>
{

    var bucket = new Bucket("my-bucket", new BucketArgs {
        Acl = CannedAcl.PublicRead,
    });

    var folder = new S3BucketFolder("synced-folder", new S3BucketFolderArgs {
        Path = "./my-folder",
        BucketName = bucket.BucketName,
        Acl = (string)CannedAcl.PublicRead,
    });
});
name: synced-folder-aws-yaml
runtime: yaml

resources:

  my-bucket:
    type: aws:s3:Bucket
    properties:
      acl: public-read

  synced-folder:
    type: synced-folder:index:S3BucketFolder
    properties:
      path: ./my-folder
      bucketName: ${my-bucket.bucket}
      acl: public-read

Sync to Azure Blob Storage

import * as resources from "@pulumi/azure-native/resources";
import * as storage from "@pulumi/azure-native/storage";
import * as synced from "@pulumi/synced-folder";

const resourceGroup = new resources.ResourceGroup("resourceGroup");

const account = new storage.StorageAccount("account", {
    resourceGroupName: resourceGroup.name,
    kind: storage.Kind.StorageV2,
    sku: {
        name: storage.SkuName.Standard_LRS,
    },
});

const container = new storage.BlobContainer("container", {
    resourceGroupName: resourceGroup.name,
    accountName: account.name,
});

const folder = new synced.AzureBlobFolder("synced-folder", {
    resourceGroupName: resourceGroup.name,
    storageAccountName: account.name,
    containerName: container.name,
    path: "./my-folder",
});
import resource
from pulumi_azure_native import storage
from pulumi_azure_native import resources
import pulumi_synced_folder

resource_group = resources.ResourceGroup("resource_group")

account = storage.StorageAccount("account", storage.StorageAccountArgs(
    resource_group_name=resource_group.name,
    kind=storage.Kind.STORAGE_V2,
    sku=storage.SkuArgs(
        name=storage.SkuName.STANDARD_LRS,
    )
))

container = storage.BlobContainer("container", storage.BlobContainerArgs(
    resource_group_name=resource_group.name,
    account_name=account.name,
))

folder = pulumi_synced_folder.AzureBlobFolder("synced-folder", pulumi_synced_folder.AzureBlobFolderArgs(
    resource_group_name=resource_group.name,
    storage_account_name=account.name,
    container_name=container.name,
    path="./my-folder",
))
package main

import (
	"github.com/pulumi/pulumi-azure-native/sdk/go/azure/resources"
	"github.com/pulumi/pulumi-azure-native/sdk/go/azure/storage"
	synced "github.com/pulumi/pulumi-synced-folder/sdk/go/synced-folder"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {

		resourceGroup, err := resources.NewResourceGroup(ctx, "resourceGroup", nil)
		if err != nil {
			return err
		}

		account, err := storage.NewStorageAccount(ctx, "account", &storage.StorageAccountArgs{
			ResourceGroupName: resourceGroup.Name,
			Kind:              pulumi.String("StorageV2"),
			Sku: &storage.SkuArgs{
				Name: pulumi.String("Standard_LRS"),
			},
		})
		if err != nil {
			return err
		}

		container, err := storage.NewBlobContainer(ctx, "container", &storage.BlobContainerArgs{
			ResourceGroupName: resourceGroup.Name,
			AccountName:       account.Name,
		})
		if err != nil {
			return err
		}

		_, err = synced.NewAzureBlobFolder(ctx, "folder", &synced.AzureBlobFolderArgs{
			ResourceGroupName:  resourceGroup.Name,
			StorageAccountName: account.Name,
			ContainerName:      container.Name,
			Path:               pulumi.String("./my-folder"),
		})
		if err != nil {
			return err
		}

		return nil
	})
}
using Pulumi.AzureNative.Resources;
using Pulumi.AzureNative.Storage;
using Pulumi.AzureNative.Storage.Inputs;
using Pulumi.SyncedFolder;

return await Pulumi.Deployment.RunAsync(() =>
{
    var resourceGroup = new ResourceGroup("resource-group");

    var storageAccount = new StorageAccount("account", new StorageAccountArgs
    {
        ResourceGroupName = resourceGroup.Name,
        Kind = Kind.StorageV2,
        Sku = new SkuArgs
        {
            Name = SkuName.Standard_LRS
        },
    });

    var container = new BlobContainer("container", new BlobContainerArgs
    {
        ResourceGroupName = resourceGroup.Name,
        AccountName = storageAccount.Name,
    });

    var folder = new AzureBlobFolder("synced-folder", new AzureBlobFolderArgs
    {
        ResourceGroupName = resourceGroup.Name,
        StorageAccountName = storageAccount.Name,
        ContainerName = container.Name,
        Path = "./my-folder",
    });
});
name: synced-folder-azure-yaml
runtime: yaml

resources:

    resourceGroup:
      type: azure-native:resources:ResourceGroup

    account:
      type: azure-native:storage:StorageAccount
      properties:
        resourceGroupName: ${resourceGroup.name}
        kind: StorageV2
        sku:
          name: Standard_LRS

    container:
      type: azure-native:storage:BlobContainer
      properties:
        resourceGroupName: ${resourceGroup.name}
        accountName: ${account.name}

    folder:
      type: synced-folder:index:AzureBlobFolder
      properties:
        resourceGroupName: ${resourceGroup.name}
        storageAccountName: ${account.name}
        containerName: ${container.name}
        path: ./my-folder

Sync to Google Cloud Storage

import * as gcp from "@pulumi/gcp";
import * as synced from "@pulumi/synced-folder";

const bucket = new gcp.storage.Bucket("my-bucket", {
    location: "US"
});

const binding = new gcp.storage.BucketIAMBinding("binding", {
    bucket: bucket.name,
    role: "roles/storage.objectViewer",
    members: [
        "allUsers"
    ],
});

const folder = new synced.GoogleCloudFolder("folder", {
    bucketName: bucket.name,
    path: "./my-folder",
});
from pulumi_gcp import storage
import pulumi_synced_folder

bucket = storage.Bucket("my-bucket", location="US")

binding = storage.BucketIAMBinding(
    "binding",
    storage.BucketIAMBindingArgs(
        bucket=bucket.name,
        role="roles/storage.objectViewer",
        members=[
            "allUsers",
        ],
    )
)

folder = pulumi_synced_folder.GoogleCloudFolder(
    "folder",
    pulumi_synced_folder.GoogleCloudFolderArgs(
        bucket_name=bucket.name,
        path="./my-folder",
    ),
)
package main

import (
	"github.com/pulumi/pulumi-gcp/sdk/v6/go/gcp/storage"
	synced "github.com/pulumi/pulumi-synced-folder/sdk/go/synced-folder"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {

		bucket, err := storage.NewBucket(ctx, "my-bucket", &storage.BucketArgs{
			Location: pulumi.String("US"),
		})
		if err != nil {
			return err
		}

		_, err = storage.NewBucketIAMBinding(ctx, "binding", &storage.BucketIAMBindingArgs{
			Bucket:  bucket.Name,
			Role:    pulumi.String("roles/storage.objectViewer"),
			Members: pulumi.ToStringArray([]string{"allUsers"}),
		})
		if err != nil {
			return err
		}

		_, err = synced.NewGoogleCloudFolder(ctx, "folder", &synced.GoogleCloudFolderArgs{
			BucketName: bucket.Name,
			Path:       pulumi.String("./my-folder"),
		})
		if err != nil {
			return err
		}

		return nil
	})
}
using Pulumi;
using Pulumi.Gcp.Storage;
using Pulumi.SyncedFolder;

return await Deployment.RunAsync(() =>
{
    var bucket = new Bucket("my-bucket", new BucketArgs
    {
        Location = "US"
    });

    var binding = new BucketIAMBinding("binding", new BucketIAMBindingArgs
    {
        Bucket = bucket.Name,
        Role = "roles/storage.objectViewer",
        Members = new[]
        {
            "allUsers",
        }
    });

    var folder = new GoogleCloudFolder("folder", new GoogleCloudFolderArgs
    {
        BucketName = bucket.Name,
        Path = "./my-folder",
    });
});
name: synced-folder-gcp-yaml
runtime: yaml

resources:

  bucket:
    type: gcp:storage:Bucket
    properties:
      location: US

  binding:
    type: gcp:storage:BucketIAMBinding
    properties:
      bucket: ${bucket.name}
      role: roles/storage.objectViewer
      members:
        - allUsers

  folder:
    type: synced-folder:index:GoogleCloudFolder
    properties:
      bucketName: ${bucket.name}
      path: ./my-folder

Notes

Managed and unmanaged file objects

By default, the Synced Folder component manages your files as individual Pulumi cloud resources (for example, as multiple aws:S3:BucketObjects), but you can opt out of this behavior by using the component’s managedObjects property:

const folder = new synced.S3BucketFolder("synced-folder", {
    path: "./my-folder",
    bucketName: bucket.bucket,
    acl: aws.s3.PublicReadAcl,

    // Set this property to false to manage files outside Pulumi.
    managedObjects: false,
});
folder = pulumi_synced_folder.S3BucketFolder(
    "synced-folder",
    path="./my-folder",
    bucket_name=bucket.bucket,
    acl=s3.CannedAcl.PUBLIC_READ,

    # Set this property to false to manage files outside Pulumi.
    managed_objects=False,
)
folder, err = synced.NewS3BucketFolder(ctx, "synced-folder", &synced.S3BucketFolderArgs{
    Path:           pulumi.String("./my-folder"),
    BucketName:     bucket.Bucket,
    Acl:            s3.CannedAclPublicRead,

    // Set this property to false to manage files outside Pulumi.
    ManagedObjects: pulumi.Bool(false),
})
var folder = new S3BucketFolder("synced-bucket-folder", new S3BucketFolderArgs {
    Path = "./my-folder",
    BucketName = bucket.BucketName,
    Acl = (string)CannedAcl.PublicRead,

    // Set this property to false to manage files outside Pulumi.
    ManagedObjects = false,
});
folder:
  type: synced-folder:index:S3BucketFolder
  properties:
    path: ./my-folder
    bucketName: ${my-bucket.bucket}

    # Set this property to false to manage files outside Pulumi.
    managedObjects: false

When you do this, the component assumes you’ve installed the cloud provider’s official CLI — aws, az, or gcloud/gsutil, depending on the cloud — and uses the Pulumi Command provider to issue commands the CLI tool directly:

Files are one-way synchronized to the cloud, and any files that exist remotely but not locally are deleted from the cloud-storage container. All files are deleted from the cloud-storage container on pulumi destroy.

If the folder you’re syncing contains a large number of files (e.g., many thousands), you may want to consider using this option.

The component does not yet support switching seamlessly between managedObjects: true and managedObjects: false, however, so if you find after deploying a given folder with managed objects that you’d prefer to use unmanaged objects instead (or vice-versa), we recommend creating a second bucket/storage container and folder and removing the first. You can generally do this within the scope of a single program update. For example:

name: synced-folder-aws-yaml
runtime: yaml

resources:

  # Original bucket and synced-folder resources, using managed file objects.
  #
  # my-first-bucket:
  #   type: aws:s3:Bucket
  #   properties:
  #     acl: public-read
  #     website:
  #       indexDocument: index.html
  #       errorDocument: error.html
  #
  # my-first-synced-folder:
  #   type: synced-folder:index:S3BucketFolder
  #   properties:
  #     path: ./stuff
  #     bucketName: ${my-first-bucket.bucket}
  #     acl: public-read

  # A new bucket and synced-folder using unmanaged file objects.
  changed-my-mind-bucket:
    type: aws:s3:Bucket
    properties:
      acl: public-read

  changed-my-mind-synced-folder:
    type: synced-folder:index:S3BucketFolder
    properties:
      path: ./stuff
      bucketName: ${changed-my-mind-bucket.bucket}
      acl: public-read
      managedObjects: false