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 configured cache and upload buffer. The gateway must have cache added (via aws.storagegateway.Cache) before volume creation. Without an upload buffer (via aws.storagegateway.UploadBuffer), volumes will be created but remain in UPLOAD BUFFER NOT CONFIGURED status and won’t be operational to clients. The examples are intentionally small. Combine them with your own gateway infrastructure and iSCSI client configuration.

Create an empty cached iSCSI volume

Most deployments start by creating a new empty volume that applications can mount and use immediately.

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

When you create the volume, Storage Gateway exposes it as an iSCSI target on the specified network interface. The targetName becomes part of the iSCSI Qualified Name (IQN) that clients use to connect. The volumeSizeInBytes sets the total capacity; frequently accessed data stays in the gateway’s local cache while all data backs up to S3. The gatewayArn references the gateway that must already have cache configured.

Restore a volume from an EBS snapshot

Teams migrating existing EBS volumes to Storage Gateway or recovering from backups can restore cached volumes directly from 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());

    }
}

The snapshotId property points to an existing EBS snapshot, and Storage Gateway restores its contents into the new cached volume. The volumeSizeInBytes must match or exceed the original snapshot size. This approach works well for migrating on-premises workloads that were previously backed up to EBS, or for disaster recovery scenarios where you need to restore data to a gateway location.

Clone an existing Storage Gateway volume

When you need to duplicate a volume for testing or create a point-in-time copy, you can clone from an existing volume’s latest 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 references an existing Storage Gateway volume, and the new volume becomes an exact copy of that volume’s latest recovery point. Unlike snapshot restore, this works directly with Storage Gateway volumes without requiring an intermediate EBS snapshot. 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 and snapshot and volume cloning. They’re intentionally minimal rather than full storage deployments.

The examples require pre-existing infrastructure such as Storage Gateway with configured cache (aws.storagegateway.Cache), upload buffer configuration (aws.storagegateway.UploadBuffer), gateway ARN and network interface references, 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)
  • Volume tagging for organization
  • CHAP authentication setup

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
What do I need to configure before creating a cached iSCSI volume?
The gateway must have cache added via aws.storagegateway.Cache before creating volumes, otherwise the Storage Gateway API will return an error.
Why is my volume showing 'UPLOAD BUFFER NOT CONFIGURED' status?
The volume was created without an upload buffer configured. While the API allows volume creation without error, the volume won’t be operational to clients until you add an upload buffer via aws.storagegateway.UploadBuffer.
How do I ensure the cache is configured before the volume is created?
Reference the aws.storagegateway.Cache resource’s gatewayArn attribute in your volume configuration, or use explicit dependsOn to declare the dependency (e.g., dependsOn: [exampleCache]).
Volume Creation & Configuration
What are my options for creating a cached iSCSI volume?

You can create a volume in three ways:

  1. Empty volume - Specify volumeSizeInBytes and targetName
  2. From snapshot - Use snapshotId with a snapshot ID
  3. From existing volume - Use sourceVolumeArn to clone a volume
How do I create a volume from an EBS snapshot?
Set snapshotId to the snapshot ID (e.g., snap-1122aabb) and calculate volumeSizeInBytes as the snapshot’s volume size in bytes: snapshotVolumeSize * 1024 * 1024 * 1024.
What's required when cloning an existing volume?
Specify sourceVolumeArn with the existing volume’s ARN. The new volume’s volumeSizeInBytes must be equal to or larger than the source volume’s size in bytes.
How do I enable KMS encryption for my volume?
Set kmsEncrypted to true and provide kmsKey with the ARN of your AWS KMS key. If kmsEncrypted is false or omitted, Amazon S3-managed encryption is used.
Constraints & Limitations
What properties can't be changed after creating a volume?
These properties are immutable: gatewayArn, networkInterfaceId, targetName, volumeSizeInBytes, kmsEncrypted, kmsKey, snapshotId, and sourceVolumeArn. Note that networkInterfaceId only accepts IPv4 addresses.
What happens if I use the same target name for multiple volumes?
The targetName must be unique across all volumes of a gateway. Using a duplicate name will cause an error.

Using a different cloud?

Explore storage guides for other cloud providers: