Deploy AWS Storage Gateway

The aws:storagegateway/gateway:Gateway resource, part of the Pulumi AWS provider, provisions a Storage Gateway appliance that bridges on-premises or cloud environments with AWS storage services. This guide focuses on three capabilities: gateway type selection (S3, FSx, tape, volume), Active Directory integration, and local cache configuration.

Storage Gateway requires a deployed VM (on-premises or EC2) that Pulumi can reach on port 80 for activation. Gateways may reference Active Directory domains or EBS volumes for cache storage. The examples are intentionally small. Combine them with your own VM deployment, networking, and storage configuration.

Deploy an S3 file gateway for NFS/SMB access

Teams often need to expose S3 buckets as network file shares for applications that expect traditional file protocols. S3 file gateways bridge this gap by presenting S3 storage through NFS or SMB.

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const example = new aws.storagegateway.Gateway("example", {
    gatewayIpAddress: "1.2.3.4",
    gatewayName: "example",
    gatewayTimezone: "GMT",
    gatewayType: "FILE_S3",
});
import pulumi
import pulumi_aws as aws

example = aws.storagegateway.Gateway("example",
    gateway_ip_address="1.2.3.4",
    gateway_name="example",
    gateway_timezone="GMT",
    gateway_type="FILE_S3")
package main

import (
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/storagegateway"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := storagegateway.NewGateway(ctx, "example", &storagegateway.GatewayArgs{
			GatewayIpAddress: pulumi.String("1.2.3.4"),
			GatewayName:      pulumi.String("example"),
			GatewayTimezone:  pulumi.String("GMT"),
			GatewayType:      pulumi.String("FILE_S3"),
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;

return await Deployment.RunAsync(() => 
{
    var example = new Aws.StorageGateway.Gateway("example", new()
    {
        GatewayIpAddress = "1.2.3.4",
        GatewayName = "example",
        GatewayTimezone = "GMT",
        GatewayType = "FILE_S3",
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.storagegateway.Gateway;
import com.pulumi.aws.storagegateway.GatewayArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var example = new Gateway("example", GatewayArgs.builder()
            .gatewayIpAddress("1.2.3.4")
            .gatewayName("example")
            .gatewayTimezone("GMT")
            .gatewayType("FILE_S3")
            .build());

    }
}
resources:
  example:
    type: aws:storagegateway:Gateway
    properties:
      gatewayIpAddress: 1.2.3.4
      gatewayName: example
      gatewayTimezone: GMT
      gatewayType: FILE_S3

The gatewayIpAddress points to your deployed Storage Gateway VM, which Pulumi contacts on port 80 to retrieve an activation key. The gatewayType of FILE_S3 configures the gateway to present S3 buckets as file shares. The gatewayTimezone sets the schedule for maintenance windows and snapshots.

Connect to FSx for Windows File Server

Organizations with FSx for Windows File Server deployments use FSx file gateways to provide low-latency local access to cloud file shares, with Active Directory integration for authentication.

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const example = new aws.storagegateway.Gateway("example", {
    gatewayIpAddress: "1.2.3.4",
    gatewayName: "example",
    gatewayTimezone: "GMT",
    gatewayType: "FILE_FSX_SMB",
    smbActiveDirectorySettings: {
        domainName: "corp.example.com",
        password: "avoid-plaintext-passwords",
        username: "Admin",
    },
});
import pulumi
import pulumi_aws as aws

example = aws.storagegateway.Gateway("example",
    gateway_ip_address="1.2.3.4",
    gateway_name="example",
    gateway_timezone="GMT",
    gateway_type="FILE_FSX_SMB",
    smb_active_directory_settings={
        "domain_name": "corp.example.com",
        "password": "avoid-plaintext-passwords",
        "username": "Admin",
    })
package main

import (
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/storagegateway"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := storagegateway.NewGateway(ctx, "example", &storagegateway.GatewayArgs{
			GatewayIpAddress: pulumi.String("1.2.3.4"),
			GatewayName:      pulumi.String("example"),
			GatewayTimezone:  pulumi.String("GMT"),
			GatewayType:      pulumi.String("FILE_FSX_SMB"),
			SmbActiveDirectorySettings: &storagegateway.GatewaySmbActiveDirectorySettingsArgs{
				DomainName: pulumi.String("corp.example.com"),
				Password:   pulumi.String("avoid-plaintext-passwords"),
				Username:   pulumi.String("Admin"),
			},
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;

return await Deployment.RunAsync(() => 
{
    var example = new Aws.StorageGateway.Gateway("example", new()
    {
        GatewayIpAddress = "1.2.3.4",
        GatewayName = "example",
        GatewayTimezone = "GMT",
        GatewayType = "FILE_FSX_SMB",
        SmbActiveDirectorySettings = new Aws.StorageGateway.Inputs.GatewaySmbActiveDirectorySettingsArgs
        {
            DomainName = "corp.example.com",
            Password = "avoid-plaintext-passwords",
            Username = "Admin",
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.storagegateway.Gateway;
import com.pulumi.aws.storagegateway.GatewayArgs;
import com.pulumi.aws.storagegateway.inputs.GatewaySmbActiveDirectorySettingsArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var example = new Gateway("example", GatewayArgs.builder()
            .gatewayIpAddress("1.2.3.4")
            .gatewayName("example")
            .gatewayTimezone("GMT")
            .gatewayType("FILE_FSX_SMB")
            .smbActiveDirectorySettings(GatewaySmbActiveDirectorySettingsArgs.builder()
                .domainName("corp.example.com")
                .password("avoid-plaintext-passwords")
                .username("Admin")
                .build())
            .build());

    }
}
resources:
  example:
    type: aws:storagegateway:Gateway
    properties:
      gatewayIpAddress: 1.2.3.4
      gatewayName: example
      gatewayTimezone: GMT
      gatewayType: FILE_FSX_SMB
      smbActiveDirectorySettings:
        domainName: corp.example.com
        password: avoid-plaintext-passwords
        username: Admin

The gatewayType of FILE_FSX_SMB enables FSx integration. The smbActiveDirectorySettings block joins the gateway to your Active Directory domain, allowing Windows authentication for file share access. The gateway caches frequently accessed files locally while storing the full dataset in FSx.

Archive to virtual tape library for backup software

Backup applications that expect tape infrastructure can write to virtual tape libraries instead, with Storage Gateway handling the translation to S3 and Glacier storage.

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const example = new aws.storagegateway.Gateway("example", {
    gatewayIpAddress: "1.2.3.4",
    gatewayName: "example",
    gatewayTimezone: "GMT",
    gatewayType: "VTL",
    mediumChangerType: "AWS-Gateway-VTL",
    tapeDriveType: "IBM-ULT3580-TD5",
});
import pulumi
import pulumi_aws as aws

example = aws.storagegateway.Gateway("example",
    gateway_ip_address="1.2.3.4",
    gateway_name="example",
    gateway_timezone="GMT",
    gateway_type="VTL",
    medium_changer_type="AWS-Gateway-VTL",
    tape_drive_type="IBM-ULT3580-TD5")
package main

import (
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/storagegateway"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := storagegateway.NewGateway(ctx, "example", &storagegateway.GatewayArgs{
			GatewayIpAddress:  pulumi.String("1.2.3.4"),
			GatewayName:       pulumi.String("example"),
			GatewayTimezone:   pulumi.String("GMT"),
			GatewayType:       pulumi.String("VTL"),
			MediumChangerType: pulumi.String("AWS-Gateway-VTL"),
			TapeDriveType:     pulumi.String("IBM-ULT3580-TD5"),
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;

return await Deployment.RunAsync(() => 
{
    var example = new Aws.StorageGateway.Gateway("example", new()
    {
        GatewayIpAddress = "1.2.3.4",
        GatewayName = "example",
        GatewayTimezone = "GMT",
        GatewayType = "VTL",
        MediumChangerType = "AWS-Gateway-VTL",
        TapeDriveType = "IBM-ULT3580-TD5",
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.storagegateway.Gateway;
import com.pulumi.aws.storagegateway.GatewayArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var example = new Gateway("example", GatewayArgs.builder()
            .gatewayIpAddress("1.2.3.4")
            .gatewayName("example")
            .gatewayTimezone("GMT")
            .gatewayType("VTL")
            .mediumChangerType("AWS-Gateway-VTL")
            .tapeDriveType("IBM-ULT3580-TD5")
            .build());

    }
}
resources:
  example:
    type: aws:storagegateway:Gateway
    properties:
      gatewayIpAddress: 1.2.3.4
      gatewayName: example
      gatewayTimezone: GMT
      gatewayType: VTL
      mediumChangerType: AWS-Gateway-VTL
      tapeDriveType: IBM-ULT3580-TD5

The gatewayType of VTL (Virtual Tape Library) presents iSCSI tape targets to backup software. The mediumChangerType and tapeDriveType properties define the virtual tape hardware that backup applications see. Storage Gateway stores tapes in S3 and archives them to Glacier automatically.

Present cached iSCSI volumes with S3 backing

Applications needing block storage can use cached volume gateways to present iSCSI targets while storing the full dataset in S3, keeping only frequently accessed data local.

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const example = new aws.storagegateway.Gateway("example", {
    gatewayIpAddress: "1.2.3.4",
    gatewayName: "example",
    gatewayTimezone: "GMT",
    gatewayType: "CACHED",
});
import pulumi
import pulumi_aws as aws

example = aws.storagegateway.Gateway("example",
    gateway_ip_address="1.2.3.4",
    gateway_name="example",
    gateway_timezone="GMT",
    gateway_type="CACHED")
package main

import (
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/storagegateway"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := storagegateway.NewGateway(ctx, "example", &storagegateway.GatewayArgs{
			GatewayIpAddress: pulumi.String("1.2.3.4"),
			GatewayName:      pulumi.String("example"),
			GatewayTimezone:  pulumi.String("GMT"),
			GatewayType:      pulumi.String("CACHED"),
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;

return await Deployment.RunAsync(() => 
{
    var example = new Aws.StorageGateway.Gateway("example", new()
    {
        GatewayIpAddress = "1.2.3.4",
        GatewayName = "example",
        GatewayTimezone = "GMT",
        GatewayType = "CACHED",
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.storagegateway.Gateway;
import com.pulumi.aws.storagegateway.GatewayArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var example = new Gateway("example", GatewayArgs.builder()
            .gatewayIpAddress("1.2.3.4")
            .gatewayName("example")
            .gatewayTimezone("GMT")
            .gatewayType("CACHED")
            .build());

    }
}
resources:
  example:
    type: aws:storagegateway:Gateway
    properties:
      gatewayIpAddress: 1.2.3.4
      gatewayName: example
      gatewayTimezone: GMT
      gatewayType: CACHED

The gatewayType of CACHED creates a volume gateway that stores all data in S3 but caches frequently accessed blocks locally. Applications connect via iSCSI and see standard block devices. This configuration requires local cache storage, configured separately.

Configure local disk as cache storage

Cached and stored volume gateways require local disk space for caching frequently accessed data. This configuration attaches an EBS volume to the gateway instance and registers it as cache storage.

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const testVolumeAttachment = new aws.ec2.VolumeAttachment("test", {
    deviceName: "/dev/xvdb",
    volumeId: testAwsEbsVolume.id,
    instanceId: testAwsInstance.id,
});
const test = aws.storagegateway.getLocalDisk({
    diskNode: testAwsVolumeAttachment.deviceName,
    gatewayArn: testAwsStoragegatewayGateway.arn,
});
const testCache = new aws.storagegateway.Cache("test", {
    diskId: test.then(test => test.diskId),
    gatewayArn: testAwsStoragegatewayGateway.arn,
});
import pulumi
import pulumi_aws as aws

test_volume_attachment = aws.ec2.VolumeAttachment("test",
    device_name="/dev/xvdb",
    volume_id=test_aws_ebs_volume["id"],
    instance_id=test_aws_instance["id"])
test = aws.storagegateway.get_local_disk(disk_node=test_aws_volume_attachment["deviceName"],
    gateway_arn=test_aws_storagegateway_gateway["arn"])
test_cache = aws.storagegateway.Cache("test",
    disk_id=test.disk_id,
    gateway_arn=test_aws_storagegateway_gateway["arn"])
package main

import (
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/ec2"
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/storagegateway"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := ec2.NewVolumeAttachment(ctx, "test", &ec2.VolumeAttachmentArgs{
			DeviceName: pulumi.String("/dev/xvdb"),
			VolumeId:   pulumi.Any(testAwsEbsVolume.Id),
			InstanceId: pulumi.Any(testAwsInstance.Id),
		})
		if err != nil {
			return err
		}
		test, err := storagegateway.GetLocalDisk(ctx, &storagegateway.GetLocalDiskArgs{
			DiskNode:   pulumi.StringRef(testAwsVolumeAttachment.DeviceName),
			GatewayArn: testAwsStoragegatewayGateway.Arn,
		}, nil)
		if err != nil {
			return err
		}
		_, err = storagegateway.NewCache(ctx, "test", &storagegateway.CacheArgs{
			DiskId:     pulumi.String(test.DiskId),
			GatewayArn: pulumi.Any(testAwsStoragegatewayGateway.Arn),
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;

return await Deployment.RunAsync(() => 
{
    var testVolumeAttachment = new Aws.Ec2.VolumeAttachment("test", new()
    {
        DeviceName = "/dev/xvdb",
        VolumeId = testAwsEbsVolume.Id,
        InstanceId = testAwsInstance.Id,
    });

    var test = Aws.StorageGateway.GetLocalDisk.Invoke(new()
    {
        DiskNode = testAwsVolumeAttachment.DeviceName,
        GatewayArn = testAwsStoragegatewayGateway.Arn,
    });

    var testCache = new Aws.StorageGateway.Cache("test", new()
    {
        DiskId = test.Apply(getLocalDiskResult => getLocalDiskResult.DiskId),
        GatewayArn = testAwsStoragegatewayGateway.Arn,
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.ec2.VolumeAttachment;
import com.pulumi.aws.ec2.VolumeAttachmentArgs;
import com.pulumi.aws.storagegateway.StoragegatewayFunctions;
import com.pulumi.aws.storagegateway.inputs.GetLocalDiskArgs;
import com.pulumi.aws.storagegateway.Cache;
import com.pulumi.aws.storagegateway.CacheArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var testVolumeAttachment = new VolumeAttachment("testVolumeAttachment", VolumeAttachmentArgs.builder()
            .deviceName("/dev/xvdb")
            .volumeId(testAwsEbsVolume.id())
            .instanceId(testAwsInstance.id())
            .build());

        final var test = StoragegatewayFunctions.getLocalDisk(GetLocalDiskArgs.builder()
            .diskNode(testAwsVolumeAttachment.deviceName())
            .gatewayArn(testAwsStoragegatewayGateway.arn())
            .build());

        var testCache = new Cache("testCache", CacheArgs.builder()
            .diskId(test.diskId())
            .gatewayArn(testAwsStoragegatewayGateway.arn())
            .build());

    }
}
resources:
  testVolumeAttachment:
    type: aws:ec2:VolumeAttachment
    name: test
    properties:
      deviceName: /dev/xvdb
      volumeId: ${testAwsEbsVolume.id}
      instanceId: ${testAwsInstance.id}
  testCache:
    type: aws:storagegateway:Cache
    name: test
    properties:
      diskId: ${test.diskId}
      gatewayArn: ${testAwsStoragegatewayGateway.arn}
variables:
  test:
    fn::invoke:
      function: aws:storagegateway:getLocalDisk
      arguments:
        diskNode: ${testAwsVolumeAttachment.deviceName}
        gatewayArn: ${testAwsStoragegatewayGateway.arn}

The getLocalDisk function identifies the attached EBS volume by its device node path. The Cache resource registers this disk with the gateway for caching. This is a prerequisite for cached volume gateways; the gateway must have cache storage before you can create cached volumes.

Beyond these examples

These snippets focus on specific gateway-level features: gateway type selection (S3, FSx, tape, cached/stored volumes), Active Directory integration for SMB, and local cache configuration. They’re intentionally minimal rather than full hybrid storage deployments.

The examples assume pre-existing infrastructure such as Storage Gateway VM deployed on-premises or in EC2, Active Directory domain for FSx gateways, and EBS volumes for cache storage. They focus on configuring the gateway rather than provisioning the underlying VM or network infrastructure.

To keep things focused, common gateway patterns are omitted, including:

  • CloudWatch logging (cloudwatchLogGroupArn)
  • Bandwidth throttling (averageUploadRateLimitInBitsPerSec, averageDownloadRateLimitInBitsPerSec)
  • SMB security strategy and guest passwords
  • Maintenance windows (maintenanceStartTime)

These omissions are intentional: the goal is to illustrate how each gateway type is wired, not provide drop-in hybrid storage modules. See the Storage Gateway Gateway resource reference for all available configuration options.

Let's deploy AWS Storage Gateway

Get started with Pulumi Cloud, then follow our quick setup guide to deploy this infrastructure.

Try Pulumi Cloud for FREE

Frequently Asked Questions

Gateway Activation & Connectivity
Why am I getting 'The specified gateway is not connected' errors during activation?
This error means your gateway instance doesn’t meet Storage Gateway requirements. Verify your gateway instance configuration before attempting activation.
What's the difference between activationKey and gatewayIpAddress for gateway activation?
You must provide one (but not both) for activation. Use activationKey if you’ve already obtained an activation key, or gatewayIpAddress to have Pulumi retrieve the key automatically. With gatewayIpAddress, Pulumi makes an HTTP request to port 80 on the specified IP.
What network access is required when using gatewayIpAddress?
Pulumi must be able to make an HTTP (port 80) GET request to the gateway IP address. Ensure port 80 is accessible from where Pulumi is running.
How do I activate a gateway in a private subnet?
Use gatewayVpcEndpoint to specify the VPC endpoint address. Pulumi must have HTTP access to this endpoint.
Gateway Types & Configuration
What gateway types are available?
Five types are supported: CACHED (cached volume), STORED (stored volume), FILE_S3 (S3 file gateway), FILE_FSX_SMB (FSx file gateway), and VTL (tape gateway). The default is STORED.
What's required to configure a tape gateway?
Set gatewayType to VTL and specify both mediumChangerType (e.g., AWS-Gateway-VTL) and tapeDriveType (e.g., IBM-ULT3580-TD5).
What timezone format does gatewayTimezone accept?
Use GMT, GMT-hr:mm, or GMT+hr:mm format. For example, GMT-4:00 indicates 4 hours behind GMT. This timezone is used for scheduling snapshots and maintenance.
SMB File Shares
What SMB configuration must be set before creating file shares?
For FILE_S3 and FILE_FSX_SMB gateways: set smbActiveDirectorySettings before creating ActiveDirectory authentication shares, and set smbGuestPassword before creating GuestAccess authentication shares.
Can Pulumi detect changes to my SMB guest password?
Pulumi can only detect whether a guest password exists, not its actual value. However, Pulumi can update the password when you change the argument.
Immutability & Lifecycle
What properties can't be changed after gateway creation?
These properties are immutable: activationKey, gatewayIpAddress, gatewayType, gatewayVpcEndpoint, mediumChangerType, and tapeDriveType. Changing them requires recreating the gateway.
Why do I see perpetual diffs for gateway_ip_address after importing?
Storage Gateway API cannot read gateway_ip_address after creation. Either omit this argument from your program or use ignore_changes to suppress the diff.
Why isn't Pulumi detecting changes to my tape gateway hardware configuration?
Pulumi cannot detect drift for mediumChangerType and tapeDriveType. You must manually verify these match your desired state.
Bandwidth & Performance
How do I limit bandwidth usage for my gateway?
Use averageDownloadRateLimitInBitsPerSec and averageUploadRateLimitInBitsPerSec to set rate limits. These are supported for CACHED, STORED, and VTL gateway types.

Using a different cloud?

Explore storage guides for other cloud providers: