OCI File Storage Service provides NFS v3 and v4 shared storage accessible from any instance in your VCN. Multiple instances mount the same file system simultaneously, making it useful for shared application data, Oracle Database Direct NFS, configuration files shared across a fleet, and log aggregation from multiple nodes. This post covers Terraform provisioning with export-level access control, snapshot policies, and NSG rules.
Step 1: File System, Mount Target, and Export
resource "oci_file_storage_file_system" "shared_app_data" {
compartment_id = var.compartment_id
availability_domain = data.oci_identity_availability_domains.ads.availability_domains[0].name
display_name = "shared-application-data"
kms_key_id = var.vault_key_id
defined_tags = { "Operations.Environment" = "production", "Operations.ManagedBy" = "terraform" }
}
resource "oci_file_storage_mount_target" "app_tier_mount" {
compartment_id = var.compartment_id
availability_domain = data.oci_identity_availability_domains.ads.availability_domains[0].name
display_name = "app-tier-mount-target"
subnet_id = var.app_subnet_id
nsg_ids = [var.file_storage_nsg_id]
}
resource "oci_file_storage_export_set" "app_export_set" {
mount_target_id = oci_file_storage_mount_target.app_tier_mount.id
display_name = "app-tier-export-set"
}
resource "oci_file_storage_export" "app_data_export" {
export_set_id = oci_file_storage_export_set.app_export_set.id
file_system_id = oci_file_storage_file_system.shared_app_data.id
path = "/app-data"
# Read-write access for the application tier
export_options {
source = var.app_subnet_cidr
access = "READ_WRITE"
require_privileged_source_port = true
identity_squash = "NONE"
is_anonymous_access_allowed = false
}
# Read-only access for the reporting tier
export_options {
source = var.reporting_subnet_cidr
access = "READ_ONLY"
require_privileged_source_port = true
identity_squash = "NONE"
is_anonymous_access_allowed = false
}
}
output "mount_target_ip" {
value = oci_file_storage_mount_target.app_tier_mount.ip_address
description = "Mount on instances: sudo mount MOUNT_IP:/app-data /mnt/app-data"
}
Step 2: NSG Rules for NFS
resource "oci_core_network_security_group_security_rule" "nfs_tcp" {
network_security_group_id = var.file_storage_nsg_id
direction = "INGRESS"
protocol = "6"
source_type = "CIDR_BLOCK"
source = var.app_subnet_cidr
tcp_options {
destination_port_range { min = 2048; max = 2050 }
}
description = "NFS v3 port range TCP"
}
resource "oci_core_network_security_group_security_rule" "nfs_udp" {
network_security_group_id = var.file_storage_nsg_id
direction = "INGRESS"
protocol = "17"
source_type = "CIDR_BLOCK"
source = var.app_subnet_cidr
udp_options {
destination_port_range { min = 2048; max = 2050 }
}
description = "NFS v3 port range UDP"
}
resource "oci_core_network_security_group_security_rule" "portmapper" {
network_security_group_id = var.file_storage_nsg_id
direction = "INGRESS"
protocol = "6"
source_type = "CIDR_BLOCK"
source = var.app_subnet_cidr
tcp_options {
destination_port_range { min = 111; max = 111 }
}
description = "NFS portmapper"
}
Step 3: Snapshot Policy
resource "oci_file_storage_filesystem_snapshot_policy" "app_data_snapshots" {
compartment_id = var.compartment_id
availability_domain = data.oci_identity_availability_domains.ads.availability_domains[0].name
display_name = "app-data-snapshot-policy"
schedules {
schedule_type = "HOURLY"
retention_duration_in_seconds = 86400
period = "HOURLY"
hour_of_day = 0
time_zone = "UTC"
time_schedule_start = "2026-10-10T00:00:00.000Z"
}
schedules {
schedule_type = "DAILY"
retention_duration_in_seconds = 604800
period = "DAILY"
hour_of_day = 2
time_zone = "UTC"
time_schedule_start = "2026-10-10T02:00:00.000Z"
}
}
resource "oci_file_storage_file_system_fs_snapshot_policy" "attach_policy" {
file_system_id = oci_file_storage_file_system.shared_app_data.id
filesystem_snapshot_policy_id = oci_file_storage_filesystem_snapshot_policy.app_data_snapshots.id
}
Step 4: Storage Alarm
resource "oci_monitoring_alarm" "nfs_storage_high" {
compartment_id = var.compartment_id
display_name = "file-storage-utilization-high"
is_enabled = true
metric_compartment_id = var.compartment_id
namespace = "oci_file_storage"
query = "FileSystemGigabytesUsed[1h]{fileSystemId = 'FS_OCID'}.max() > 800"
severity = "WARNING"
pending_duration = "PT1H"
destinations = [var.ops_notification_topic_id]
body = "File Storage System exceeds 800 GB. Review growth patterns and consider expanding capacity."
}
Operational Notes
Configure exports with require_privileged_source_port = true and is_anonymous_access_allowed = false. Privileged source port means only the OS kernel NFS client can mount the export. Anonymous access disabled prevents unmapped UIDs from silently receiving anonymous permissions, which can allow unintended write access when UID mappings differ between the NFS client host and the export configuration.
NFS performance scales with the size of individual IO operations, not the file system capacity. For Oracle Database Direct NFS, mount with rsize=1048576,wsize=1048576 to maximize throughput per operation. File Storage does not limit throughput based on capacity, so a small file system delivers the same per-operation performance as a large one.
Regards,
Osama
#OCI #OracleCloud #FileStorage #NFS #Terraform #IaC #TechBlog #Oracle #PlatformEngineering #SharedStorage #CloudStorage #OracleCloudInfrastructure #SnapshotPolicy #Encryption #DirectNFS #OracleDatabase #HighAvailability #CloudNative #StorageEngineering #PersistentStorage
Leave a Reply