Step 2: Create the Database Infrastructure
What This Step Does
In this step you build three things inside your AWS account:
- Networking — a private network (VPC) where Neptune will live, plus a gateway so Neptune can reach your S3 bucket.
- Database — an Amazon Neptune cluster with one database instance.
- Permissions — an IAM role that lets Neptune read data files from S3.
After this step, Neptune will be online and ready to receive data.
Why does Neptune need its own network?
Neptune is designed to be private — it is not exposed to the public internet. This is a security feature. The downside is that you cannot connect to it directly from your laptop. In Step 3 you will create a small helper machine (EC2 instance) inside the same network to run commands against Neptune.
Shortcut: Automated Scripts
If you prefer not to click through the AWS Console, you can run two scripts that create everything automatically:
export AWS_PROFILE=cloudbank-demo-admin
set -a && source .env && set +a
# 1. Create the IAM role that lets Neptune read from S3
bash scripts/06_create_neptune_load_role.sh
# 2. Create the VPC networking, Neptune cluster, and instance.
# This script waits until the instance is Available (5-15 minutes),
# then attaches the IAM role to the cluster and verifies the result.
bash scripts/07_create_neptune_cluster.sh
# 3. Confirm the database instance is ready (returns immediately after step 2)
aws neptune wait db-instance-available \
--db-instance-identifier cloudbank-biobricks-neptune-1 \
--region us-west-2
These scripts auto-detect your default VPC and subnets, create the S3 gateway endpoint, and write NEPTUNE_ENDPOINT and NEPTUNE_IAM_ROLE_ARN into your .env file.
The scripts create the Neptune instance with class db.r5.large by default (the class recommended for this dataset, see the Goal section below). If you want a cheaper smaller class anyway, override it: NEPTUNE_INSTANCE_CLASS=db.t3.medium bash scripts/07_create_neptune_cluster.sh.
scripts/07_create_neptune_cluster.sh prints an explicit result for the role association: either Role is associated to cluster or a warning with the exact command to re-run if the role was not attached yet (this only happens rarely, when IAM propagation lags the cluster readiness).
If you use the scripts, skip to Section 7 — Verify to confirm it worked.
Goal
By the end of this step you have:
| Resource | Value |
|---|---|
| Neptune cluster | cloudbank-biobricks-neptune in us-west-2 |
| Database instance | db.r5.large (2 vCPU, 16 GiB RAM), status = Available |
| Security group | Allows inbound TCP on port 8182 from your private network |
| IAM role | NeptuneLoadFromS3Role with S3 read access |
.env updated | NEPTUNE_ENDPOINT and NEPTUNE_IAM_ROLE_ARN filled in |
Why
db.r5.large? The full OREGANO file is large (about 114 MB, over 3 million triples), and a small instance class can run out of memory during the bulk load in Step 3, failing withOUT_OF_MEMORY_ERRORbefore the data is imported.db.r5.large(2 vCPU, 16 GiB) has 4 times the memory ofdb.t3.mediumand loads the file reliably in about 10 minutes. It costs a few tens of cents per hour while it is running, and only for the hours the database exists (see the Amazon Neptune pricing page for the exact rate in your region). Smaller classes such asdb.t3.mediumordb.t4g.mediummay still work, but they can hit out-of-memory on the full dataset.
Permission Note
The regular cloudbank-demo profile may not have enough permissions to create VPCs, Neptune clusters, or IAM roles. If you see AccessDenied or UnauthorizedOperation, switch to the admin profile:
export AWS_PROFILE=cloudbank-demo-admin
Manual Console Walkthrough
Use this section if you want to understand each piece, or if the automated scripts do not work in your environment.
1) Set Up Networking (VPC)
You can use an existing default VPC or create a new one. Neptune needs:
- At least 2 private subnets in different Availability Zones (AZs).
- An S3 Gateway endpoint so Neptune can read from S3 without going through the public internet.
In the AWS Console:
- Open the VPC service in
us-west-2. - Select or create a VPC.
- Make sure it has at least two subnets in different AZs.
- Create an S3 endpoint:
- Go to Endpoints → Create endpoint.
- Service:
com.amazonaws.us-west-2.s3 - Type: Gateway
- Attach the route tables used by your subnets.
Why two subnets? Neptune requires a "DB subnet group" that spans multiple AZs for availability, even though this demo only runs one instance.
2) Create a Neptune Subnet Group
A subnet group tells Neptune which subnets it is allowed to use.
- Open Amazon Neptune → Subnet groups → Create DB subnet group.
- Name:
neptune-demo-subnet-group. - Select your VPC.
- Add at least two private subnets in different AZs.
3) Create a Security Group
A security group acts as a firewall. You need one that allows traffic on port 8182 (Neptune's port).
- Open EC2 → Security Groups → Create security group.
- Name:
neptune-demo-sg. - VPC: same VPC as above.
- Add an inbound rule:
- Type: Custom TCP
- Port:
8182 - Source: your VPC's CIDR range (this is what the automated scripts in the Shortcut section create).
- Leave outbound rules as the default (allow all).
Tip: In Step 3 the EC2 runner is placed in this same security group, so it can already reach Neptune on port 8182. The VPC CIDR source is fine for this demo; a security-group source is more secure if you want to limit access to specific machines.
4) Create the Neptune Cluster and Instance
- Open Amazon Neptune → Databases → Create database.
- Engine: Neptune.
- Cluster identifier:
cloudbank-biobricks-neptune. - Instance class:
db.r5.large(2 vCPU, 16 GiB). The smallerdb.t3.mediumanddb.t4g.mediumclasses are cheaper but can run out of memory during the bulk load in Step 3, so this demo usesdb.r5.large. - DB subnet group:
neptune-demo-subnet-group. - VPC security group:
neptune-demo-sg. - IAM DB authentication: Enable.
- Public access: Disable (recommended).
- Click Create database.
Wait until the status changes from creating to Available. This typically takes 5–15 minutes.
5) Create an IAM Role for the Bulk Loader
Neptune does not use your credentials to read from S3. Instead, it assumes an IAM role that you create. This role grants Neptune read-only access to your S3 bucket.
- Open IAM → Roles → Create role.
- Trusted entity type: AWS service.
- Service: RDS (Neptune uses the RDS trust path internally).
- Attach policy: AmazonS3ReadOnlyAccess.
- Role name:
NeptuneLoadFromS3Role. - Click Create role and copy the role's ARN (e.g.,
arn:aws:iam::123456789012:role/NeptuneLoadFromS3Role).
Then attach the role to the cluster. Do this only after the cluster status is Available; adding it while the cluster is still creating fails silently:
aws neptune add-role-to-db-cluster \
--region us-west-2 \
--db-cluster-identifier cloudbank-biobricks-neptune \
--role-arn arn:aws:iam::<your-account-id>:role/NeptuneLoadFromS3Role
6) Update Your .env File
Add the two new values:
NEPTUNE_ENDPOINT=<your-cluster-endpoint>
NEPTUNE_IAM_ROLE_ARN=arn:aws:iam::<your-account-id>:role/NeptuneLoadFromS3Role
Then reload:
set -a
source .env
set +a
7) Verify Everything Is Ready
Run these two commands to confirm the cluster and role exist:
# Print the Neptune endpoint
aws neptune describe-db-clusters \
--region us-west-2 \
--query "DBClusters[?DBClusterIdentifier=='cloudbank-biobricks-neptune'].Endpoint" \
--output text
# Print the IAM role ARN
aws iam get-role \
--role-name NeptuneLoadFromS3Role \
--query "Role.Arn" \
--output text
If both return values, you are ready for Step 3.
Check the role is attached to the cluster
The bulk loader uses the role only when it is attached to the cluster. The shortcut script does this automatically after the instance is Available; on the manual path you ran the attachment command in Section 5:
aws neptune describe-db-clusters \
--region us-west-2 \
--db-cluster-identifier cloudbank-biobricks-neptune \
--query 'DBClusters[0].AssociatedRoles' \
--output text
If this prints your role ARN, the load role is attached. If it prints nothing, run the attachment command from Section 5 again (the cluster status must be Available).
Check the TLS certificate is valid (pre-check)
Neptune serves a TLS certificate on port 8182. On rare occasions a brand-new cluster is assigned a certificate whose validity period has already ended, and then every TLS connection fails with certificate has expired (the bulk loader, curl, and Python clients are all affected). There is no console option or API to rotate the certificate, so it is worth checking before the first load.
The endpoint is only reachable from inside the VPC, so you run this command on the small EC2 runner that Step 3 creates, right before the bulk load (Step 3 shows how to send it to the runner):
openssl s_client -connect $NEPTUNE_ENDPOINT:8182 -servername $NEPTUNE_ENDPOINT -showcerts </dev/null 2>/dev/null | openssl x509 -noout -dates
The output looks like:
notBefore=Jul 27 00:00:00 2026 GMT
notAfter=Feb 9 23:59:59 2027 GMT
If notAfter is in the past (or the command reports certificate has expired), see Step 3 Troubleshooting: delete and recreate the cluster, or contact AWS support. With a valid certificate, the documented load and query steps work without any changes.
Troubleshooting
| Problem | Likely cause | Fix |
|---|---|---|
AccessDenied when creating resources | Wrong profile | Run export AWS_PROFILE=cloudbank-demo-admin |
| Neptune endpoint is empty | Cluster is still provisioning | Wait a few minutes and re-run the describe command |
| IAM role ARN missing | Role was not created | Go back to Section 5 and create it |
| Loader later says "can't find credential for iam_role_arn" | Role is not attached to Neptune or the STS endpoint is missing in the VPC | Attach the role while the cluster is Available: aws neptune add-role-to-db-cluster ... (verify with the check in Section 7) and create an STS interface endpoint (com.amazonaws.us-west-2.sts) in the VPC |