Configure AWS Storage Gateway Cached iSCSI Volumes

The aws:storagegateway/cachesIscsiVolume:CachesIscsiVolume resource, part of the Pulumi AWS provider, provisions cached iSCSI volumes that store frequently accessed data locally on the gateway while backing all data to S3. This guide focuses on three capabilities: creating empty volumes, restoring from EBS snapshots, and cloning existing volumes.

Cached iSCSI volumes require a Storage Gateway with cache and upload buffer already configured. The gateway must have cache added (via aws.storagegateway.Cache) before creating volumes, and an upload buffer (via aws.storagegateway.UploadBuffer) before volumes become operational to clients. The examples are intentionally small. Combine them with your own gateway infrastructure and encryption configuration.

Create an empty cached iSCSI volume

Most deployments start by creating a new empty volume that applications can mount and use immediately, with frequently accessed data cached locally.

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

const example = new aws.storagegateway.CachesIscsiVolume("example", {
    gatewayArn: exampleAwsStoragegatewayCache.gatewayArn,
    networkInterfaceId: exampleAwsInstance.privateIp,
    targetName: "example",
    volumeSizeInBytes: 5368709120,
});
import pulumi
import pulumi_aws as aws

example = aws.storagegateway.CachesIscsiVolume("example",
    gateway_arn=example_aws_storagegateway_cache["gatewayArn"],
    network_interface_id=example_aws_instance["privateIp"],
    target_name="example",
    volume_size_in_bytes=5368709120)
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.NewCachesIscsiVolume(ctx, "example", &storagegateway.CachesIscsiVolumeArgs{
			GatewayArn:         pulumi.Any(exampleAwsStoragegatewayCache.GatewayArn),
			NetworkInterfaceId: pulumi.Any(exampleAwsInstance.PrivateIp),
			TargetName:         pulumi.String("example"),
			VolumeSizeInBytes:  pulumi.Int(5368709120),
		})
		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.CachesIscsiVolume("example", new()
    {
        GatewayArn = exampleAwsStoragegatewayCache.GatewayArn,
        NetworkInterfaceId = exampleAwsInstance.PrivateIp,
        TargetName = "example",
        VolumeSizeInBytes = 5368709120,
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.storagegateway.CachesIscsiVolume;
import com.pulumi.aws.storagegateway.CachesIscsiVolumeArgs;
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 CachesIscsiVolume("example", CachesIscsiVolumeArgs.builder()
            .gatewayArn(exampleAwsStoragegatewayCache.gatewayArn())
            .networkInterfaceId(exampleAwsInstance.privateIp())
            .targetName("example")
            .volumeSizeInBytes(5368709120)
            .build());

    }
}
resources:
  example:
    type: aws:storagegateway:CachesIscsiVolume
    properties:
      gatewayArn: ${exampleAwsStoragegatewayCache.gatewayArn}
      networkInterfaceId: ${exampleAwsInstance.privateIp}
      targetName: example
      volumeSizeInBytes: 5.36870912e+09 # 5 GB

The volume exposes an iSCSI target on the gateway’s network interface. The targetName identifies the iSCSI target for initiator connections and must be unique across all volumes on the gateway. The volumeSizeInBytes sets the total capacity in bytes. The gatewayArn references the Storage Gateway that hosts this volume.

Restore a volume from an EBS snapshot

Teams migrating existing EBS volumes to Storage Gateway or recovering from backups can restore volumes from EBS snapshots.

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

const example = new aws.storagegateway.CachesIscsiVolume("example", {
    gatewayArn: exampleAwsStoragegatewayCache.gatewayArn,
    networkInterfaceId: exampleAwsInstance.privateIp,
    snapshotId: exampleAwsEbsSnapshot.id,
    targetName: "example",
    volumeSizeInBytes: exampleAwsEbsSnapshot.volumeSize * 1024 * 1024 * 1024,
});
import pulumi
import pulumi_aws as aws

example = aws.storagegateway.CachesIscsiVolume("example",
    gateway_arn=example_aws_storagegateway_cache["gatewayArn"],
    network_interface_id=example_aws_instance["privateIp"],
    snapshot_id=example_aws_ebs_snapshot["id"],
    target_name="example",
    volume_size_in_bytes=example_aws_ebs_snapshot["volumeSize"] * 1024 * 1024 * 1024)
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.NewCachesIscsiVolume(ctx, "example", &storagegateway.CachesIscsiVolumeArgs{
			GatewayArn:         pulumi.Any(exampleAwsStoragegatewayCache.GatewayArn),
			NetworkInterfaceId: pulumi.Any(exampleAwsInstance.PrivateIp),
			SnapshotId:         pulumi.Any(exampleAwsEbsSnapshot.Id),
			TargetName:         pulumi.String("example"),
			VolumeSizeInBytes:  int(exampleAwsEbsSnapshot.VolumeSize * 1024 * 1024 * 1024),
		})
		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.CachesIscsiVolume("example", new()
    {
        GatewayArn = exampleAwsStoragegatewayCache.GatewayArn,
        NetworkInterfaceId = exampleAwsInstance.PrivateIp,
        SnapshotId = exampleAwsEbsSnapshot.Id,
        TargetName = "example",
        VolumeSizeInBytes = exampleAwsEbsSnapshot.VolumeSize * 1024 * 1024 * 1024,
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.storagegateway.CachesIscsiVolume;
import com.pulumi.aws.storagegateway.CachesIscsiVolumeArgs;
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 CachesIscsiVolume("example", CachesIscsiVolumeArgs.builder()
            .gatewayArn(exampleAwsStoragegatewayCache.gatewayArn())
            .networkInterfaceId(exampleAwsInstance.privateIp())
            .snapshotId(exampleAwsEbsSnapshot.id())
            .targetName("example")
            .volumeSizeInBytes(exampleAwsEbsSnapshot.volumeSize() * 1024 * 1024 * 1024)
            .build());

    }
}

When you specify a snapshotId, the volume restores data from that EBS snapshot. The volumeSizeInBytes must match or exceed the snapshot’s original volume size. This approach lets you migrate existing EBS data to Storage Gateway’s hybrid storage model.

Clone an existing Storage Gateway volume

Applications that need test environments or data replication can clone existing Storage Gateway volumes to create exact copies at a specific recovery point.

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

const example = new aws.storagegateway.CachesIscsiVolume("example", {
    gatewayArn: exampleAwsStoragegatewayCache.gatewayArn,
    networkInterfaceId: exampleAwsInstance.privateIp,
    sourceVolumeArn: existing.arn,
    targetName: "example",
    volumeSizeInBytes: existing.volumeSizeInBytes,
});
import pulumi
import pulumi_aws as aws

example = aws.storagegateway.CachesIscsiVolume("example",
    gateway_arn=example_aws_storagegateway_cache["gatewayArn"],
    network_interface_id=example_aws_instance["privateIp"],
    source_volume_arn=existing["arn"],
    target_name="example",
    volume_size_in_bytes=existing["volumeSizeInBytes"])
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.NewCachesIscsiVolume(ctx, "example", &storagegateway.CachesIscsiVolumeArgs{
			GatewayArn:         pulumi.Any(exampleAwsStoragegatewayCache.GatewayArn),
			NetworkInterfaceId: pulumi.Any(exampleAwsInstance.PrivateIp),
			SourceVolumeArn:    pulumi.Any(existing.Arn),
			TargetName:         pulumi.String("example"),
			VolumeSizeInBytes:  pulumi.Any(existing.VolumeSizeInBytes),
		})
		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.CachesIscsiVolume("example", new()
    {
        GatewayArn = exampleAwsStoragegatewayCache.GatewayArn,
        NetworkInterfaceId = exampleAwsInstance.PrivateIp,
        SourceVolumeArn = existing.Arn,
        TargetName = "example",
        VolumeSizeInBytes = existing.VolumeSizeInBytes,
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.storagegateway.CachesIscsiVolume;
import com.pulumi.aws.storagegateway.CachesIscsiVolumeArgs;
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 CachesIscsiVolume("example", CachesIscsiVolumeArgs.builder()
            .gatewayArn(exampleAwsStoragegatewayCache.gatewayArn())
            .networkInterfaceId(exampleAwsInstance.privateIp())
            .sourceVolumeArn(existing.arn())
            .targetName("example")
            .volumeSizeInBytes(existing.volumeSizeInBytes())
            .build());

    }
}
resources:
  example:
    type: aws:storagegateway:CachesIscsiVolume
    properties:
      gatewayArn: ${exampleAwsStoragegatewayCache.gatewayArn}
      networkInterfaceId: ${exampleAwsInstance.privateIp}
      sourceVolumeArn: ${existing.arn}
      targetName: example
      volumeSizeInBytes: ${existing.volumeSizeInBytes}

The sourceVolumeArn points to an existing Storage Gateway volume. The new volume becomes an exact copy of the source volume’s latest recovery point. The volumeSizeInBytes must equal or exceed the source volume’s size.

Beyond these examples

These snippets focus on specific volume-level features: empty volume creation, snapshot and volume cloning, and iSCSI target configuration. They’re intentionally minimal rather than full storage deployments.

The examples rely on pre-existing infrastructure such as Storage Gateway with cache and upload buffer configured, gateway network interfaces (private IPs), and EBS snapshots or existing volumes for restore/clone scenarios. They focus on configuring the volume rather than provisioning the gateway infrastructure.

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

  • KMS encryption configuration (kmsEncrypted, kmsKey)
  • Resource tagging (tags)
  • CHAP authentication setup
  • Volume resizing or modification

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

Let's configure AWS Storage Gateway Cached iSCSI Volumes

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

Try Pulumi Cloud for FREE

Frequently Asked Questions

Prerequisites & Dependencies
Why does my volume creation fail with an API error?
The gateway must have cache added before creating volumes. Add cache using the aws.storagegateway.Cache resource, or the Storage Gateway API will return an error.
Why is my volume status showing 'UPLOAD BUFFER NOT CONFIGURED'?
The gateway needs an upload buffer added via aws.storagegateway.UploadBuffer for the volume to be operational to clients. The API allows volume creation without it, but the volume won’t function until the buffer is configured.
How do I ensure the cache is added before creating the volume?
Reference the aws.storagegateway.Cache resource’s gatewayArn attribute in your volume configuration, or use an explicit dependsOn dependency to ensure proper ordering.
Volume Creation Options
What are the three ways to create a cached iSCSI volume?
You can create an empty volume (using just volumeSizeInBytes), restore from an EBS snapshot (using snapshotId), or clone an existing volume (using sourceVolumeArn).
How do I calculate the volume size when restoring from a snapshot?
Multiply the snapshot’s volume size in GB by 1073741824 (1024 * 1024 * 1024) to convert to bytes for the volumeSizeInBytes parameter.
What's the size requirement when cloning from a source volume?
The new volume’s volumeSizeInBytes must be equal to or larger than the source volume’s size in bytes.
Configuration & Constraints
What properties can't be changed after volume creation?
Most properties are immutable: gatewayArn, networkInterfaceId, targetName, volumeSizeInBytes, kmsEncrypted, kmsKey, snapshotId, and sourceVolumeArn. Only region and tags can be modified after creation.
How do I enable KMS encryption for my volume?
Set kmsEncrypted to true and provide the KMS key ARN in kmsKey. Both properties are required together and cannot be changed after creation.
What constraints apply to targetName and networkInterfaceId?
The targetName must be unique across all volumes of a gateway, and networkInterfaceId only accepts IPv4 addresses.

Using a different cloud?

Explore storage guides for other cloud providers: