Create multiple instances with one Terraform module in Oracle Cloud Infrastructure

Since I’ve become a big fan of modules a case where you might need them is by creating multiple instances at the same time for your Terraform project.

One option is that you have the main Terraform project and call the modules from projects file multiple times like this:

module “CreatePublicInstance1” {
module “CreatePublicInstance2” {
module “CreatePublicInstance3” {
But there is other option where you use the count variable in a resource. You can easily scale the amount of resources by providing the count parameter to the module and it will loop the resource as many times you have defined.
Note that at this time there are some limitations with count – it can not be a value from other module but rather you need to define it as a static variable.

Terraform project for creating instances

Again I have three files in my Terraform project named “Create_three_instances”. The files are:


Let’s take a look of them.

In the I have defined the necessary variables for this project. In this case when I’m creating instances I have some variables pointing to existing resources related to compartment, network and instance image/shape. These are required as in the I will need to get existing OCID’s for subnets, ADs etc. I have also defined a variable server_count which is the amount of servers I want to create in this example.

In the I get existing data by using the variables.


As you can see I first get the compartment information by passing tenancy_ocid and filter the result by compartment name. After that I use the compartment OCID to get Availability Domains and VCNs.

With compartment and VCN OCID I get the subnet information and limit the result with the subnet name. Lookups are very easy to use to get already existing data created by my network project earlier!

Next I call the actual module which creates the instances.


Calling this module I pass the server_count variable, some data through lookups which are required and also some static variables which were defined in the

One additional thing to note is the instance_create_vnic_details_private_ip. As you can see I actually pass the cidr_block of the subnet. Why? You will see when the actual module is being used.

Finally in the I will just print out the instance name, public and private IPs.


Using count when creating the resource

Now by looking the actual resource “oci_core_instance” “CreateInstance” you can see the usage of the count.


I pass the server_count variable to count in the start of the resource and this determines how many times the create resource will be looped through.

As you might want to or you have to separate the naming for some resources I’ve used  ${count.index} in some places to have separation without creating a list of variables in the project’s In my opinion list might hard to keep updated and by using the numbering you can still have different naming.

If you take a look on the private_ip variable you see now the usage of the subnet cidr block I mentioned earlier. I use built-in function cidrhost to give specific private IP addresses for each host. You could let OCI to allocate those automatically but in some cases you want to define them.

Definition of cidrhost is as follows:

cidrhost(iprange, hostnum) – Takes an IP address range in CIDR notation and creates an IP address with the given host number. If given host number is negative, the count starts from the end of the range. For example, cidrhost(“”, 2) returns and cidrhost(“”, -2) returns

Reason why I have +3 in the end is that Oracle reserves two first IP addresses in the start of every subnet so the first instance will receive .3 IP address and continue to grow from there.

I also have in the module. There I need to use splat syntax (*) to send the list out.

output “instancePublicIP” {value = [“${oci_core_instance.CreateInstance.*.public_ip}”]}

Creating the resources

Now when I’m ready to create the resource I will run the usual terraform init, plan and apply. If you want to know more about those check my earlier blog post and video from here.

When I run terraform plan it will show that it will create three new resources:

Plan: 3 to add, 0 to change, 0 to destroy.

That matches the server_count variable. Once I’ve executed apply the outputs for the instances are:

Apply complete! Resources: 3 added, 0 changed, 0 destroyed.


instanceName = [
instancePrivateIP = [,,
instancePublicIP = [


Here is video also showing the exact same thing as above. You can see the code as well as running the Terraform project.


When you use Terraform modules the main goal would be to make easily repeatable code for your infrastructure. As Terraform isn’t always so flexible to different cases using count is one way to scale your resources up based on the need.

Lot of valuable information about Terraform I’ve got and used also here is from Yevgeniy Brikman’s book Terraform: Up and Running: Writing Infrastructure as CodeDefinitely buy it if you are interested to learn more!


Create DB system to Oracle Cloud Infrastructure with Terraform

One of the most important areas with Oracle Cloud Infrastructure is that you can utilize Oracle databases easily as part of your infrastructure. There are different options how you can provision the databases but as Terraform is the supported orchestration tool with OCI you should investigate if using it is feasible for your project.

This way all the databases created in your tenancy will be done through Terraform in the same way specially if you utilize Terraform modules which are sort of pre-build parts which are written for standard operations. When different “projects” utilize those modules they always call them in the same way and there is no need to write them again each time.

In this post I’ll share simple way of creating the module and using it to create a database.

Basics of DB systems in OCI

In OCI you have different options for your DB system. It can run either on VM, BM or Exadata instance. For each there are variety of different shapes available in terms of CPU, Memory, storage available etc. (And different Exadata shapes obviously)

For VMs you can create 2-node RAC configuration.

Database editions which are available:

  • Standard Edition
  • Enterprise Edition
  • Enterprise Edition – High Performance
  • Enterprise Edition – Extreme Performance (required for 2-node RAC DB systems)

And currently supported DB versions:

  • Oracle Database 18c Release 1 (18.1)
  • Oracle Database 12c Release 2 (12.2)
  • Oracle Database 12c Release 1 (12.1)
  • Oracle Database 11g Release 2 (11.2)

For licensing you have the option either to bring your own license (BYOL) or have the license included. This reflects in the cost of your system.

One important thing to remember is that the database is deployed into public subnet. You don’t need to define Internet Gateway but the database system requires public subnet. I would imagine some people don’t like this idea so much even though you can limit the access completely from outside.

Whole DB service documentation can be found from here.

Terraform setup

I’ve placed necessary Terraform files inside two folders. The module folder is named“database” and inside it I have two files:

  • – this one has code which calls resource oci_database_db_system to create the DB system
  • – this one sends variables out that I want

The project folder is named “create_db_system”  and it has three files inside.

  • – this one gets necessary data from different sources and calls database module by passing different variables which are specific to this case
  • – all the custom variables defined in this file
  • – outputs which I want to print after successfully running Terraform

Terraform module

Setting up the database module is fairly straightforward. Good example case can be found from OCI Terraform documentation here.

Note that the required parameter for resource oci_database_db_system data_storage_size_in_gb is named elsewhere as data_storage_size_in_gbbut seems correct way of calling it is without the s.

I’ve used the module so that I don’t do any data gathering inside the module but rather pass only “ready” variables into it.

My resource looks like this:


resource "oci_database_db_system" "CreateDBSystem" {


    availability_domain = "${var.db_system_availability_domain}"

    compartment_id = "${var.compartment_id}"

    cpu_core_count = "${var.db_system_cpu_core_count}"

    database_edition = "${var.db_system_database_edition}"

    db_home {


        database {


            admin_password = "${var.db_system_db_home_database_admin_password}"

            db_name = "${var.db_system_db_home_database_db_name}"

            character_set = "${var.db_system_db_home_database_character_set}"

            db_backup_config {

            ncharacter_set = "${var.db_system_db_home_database_ncharacter_set}"

            pdb_name = "${var.db_system_db_home_database_pdb_name}"


        db_version = "${var.db_system_db_home_db_version}"


        display_name = "${var.db_system_db_home_display_name}"



    shape = "${var.db_system_shape}"

    ssh_public_keys = ["${var.ssh_public_key}"]

    subnet_id = "${var.db_subnet_id}"

    data_storage_percentage = "${var.db_system_data_storage_percentage}"

    data_storage_size_in_gb = "${var.db_system_data_storage_size_in_gbs}" #For VMs only

    license_model = "${var.db_system_license_model}"

    node_count = "${var.db_system_node_count}"


Terraform project

For the project itself I’ve defined all the necessary variables inside – either these variables are passed to database module or I use them to get specific OCID’s what I require further on.

You define default variable like this:

variable “compartment_display_name” {default = “PrivDBCompartment”}

In the I fetch data using variables like here to get the compartment data:


data "oci_identity_compartments" "GetCompartments" {

#Required - tenancy OCID

  compartment_id = "${var.tenancy_ocid}"



values= ["${var.compartment_display_name}"]



This one is bit misleading as I pass tenancy OCID as compartment_id to get filtered list of available compartments.

The compartment is then used to get specific DB subnet I want to use for the DB system:


data "oci_core_subnets" "GetDBSubnet" {


  compartment_id = "${lookup(data.oci_identity_compartments.GetCompartments.compartments[0],"id")}"



To understand what variables you need to pass to get list of subnets you can see the documentation example. Same applies to all other list requests, check the documentation!


To use the data you then use lookups to get specific values you want. I didn’t understand this in the start how it works but all available variables can be seen from documentation and then you can just use it like above to get required variables for availability domains etc.

Another good example how to use lookup is to have the necessary db_system_availability_domain variable defined from above GetDBSubnet:


db_system_availability_domain = 


I’ve defined the GetDBSubnet earlier and now I just fetch the availability_domain the subnet belongs to. This way you can get all the variables related to some specific resource.

From the still few settings to point out to this exercise:

“db_system_database_edition” {default = “ENTERPRISE_EDITION”}
“db_system_db_home_db_version” {default = “”}
“db_system_shape” {default = “VM.Standard2.1”}
“db_system_license_model” {default = “LICENSE_INCLUDED”}

My database will be a VM 2.1 shape with Enterprise Edition database with license included option.

Creating the Database system

Now I’m ready to create the DB system. As usual I will run first terraform init to initialize the modules and the OCI provider for terraform.

After that I will run terraform plan to see what changes will be applied and this time I will have only 1 change to add. I’ve created compartment with network already earlier using different terraform project.

Plan: 1 to add, 0 to change, 0 to destroy.

After that I will run terraform apply to do the actual changes. Creating the database took around 70 minutes in total with my configuration.

module.cr_private_db.oci_database_db_system.CreateDBSystem: Creation complete after 1h12m42s (ID:…34qsdaozzgyb254hnr7w6nlkpaz7tshpfdy2oa)

Since I’ve set my ssh keys I can login to the DB system by providing my private key with opc user. If I look pmon processes:

[oracle@hostsimo dbhome_1]$ ps -ef|grep pmon|grep oracle
oracle 65038 47622 0 08:38 pts/0 00:00:00 grep pmon
oracle 76130 1 0 08:13 ? 00:00:00 ora_pmon_SIMO1

Besides grid ASM there is my database now also running.



Thing to remember is that if you want to allow connections to listener port modify your security lists to allow traffic! This should be part of your network setup.

After the test I’ll run terraform destroy to remove the database and that completes in bit over 7 minutes.

module.cr_private_db.oci_database_db_system.CreateDBSystem: Destruction complete after 7m19s



You can easily automatize your database creation through Terraform modules. Initial module setup takes some time so that you figure out the parameters you need but once that is done you get all your databases created in standardized way!

This was a simple example but for real world cases modules will be more complex and will require more work obviously.

Deep dive into OCI with compartments, users, groups and policies

I’ve said it before and I’ll state it once more. One of the best services with Oracle Cloud Infrastructure is Identity & Access Management. Oracle has made managing your tenancy really easy with compartments and policies.

With compartments you can manage your single account without too much of complexity by restricting user access to specific compartments and giving only access for users inside that compartment.

For example you have a user who can only use network resources in a compartment so the user can launch new instances. This way you leave administration of network components to your OCI network admins.

IAM documentation can be found from here. It’s a very well structured documentation so I would give it a read when you start working with OCI IAM.


Policies are what define who can access resource in a specific way at a group or compartment level. You always allow with your policies and never deny as by default OCI access is defined by least privilege so if something isn’t allowed it is denied. There are four level of access:

  • Inspect – you can list resources but not get the metadata or any confidential information (with network resources you still get all information!)
  • Read – Same as Inspect but you get also the metadata and the resource itself
  • Use – Includes read but has the ability so you can use the resource which varies by resource type. Includes also update of the resource apart from when update is the same as create (Updating security list for example)
  • Manage – Includes all permissions to resource

Policy syntax is as follows:

Allow <subject> to <verb> <resource-type> in <location> where <conditions>

Which translates to:

Allow group network-admins to manage virtual-network-family in tenancy

Or if you would like to give compartment level network admin access to network components only:

Allow group projectx-network-admins to manage virtual-network-family in 
compartment ProjectX

Some resources such as users and groups are managed on tenancy level.

I’ve used a resource example which is type of a “family”. This has all related resources combined inside so you don’t need to go and allow access one by one to all network resources. Advantage is that if Oracle adds more components to network resources these will be automatically added to “family” and you don’t need to go and specify them.

But if you wan’t to give access only to individual resource-type this is certainly possible! Just be careful it doesn’t come too much of work in the long run. Policy reference with family resource-types are listed here.

So what if you have more requirements – for example you want to limit group access only to modify specific compartments. This is possible with conditions.

Online documentation says this about them:


Specify one or more conditions. Use any or all with multiple conditions for a logical OR or AND, respectively.

Syntax for a single condition: variable =|!= value

Syntax for multiple conditions: any|all {<condition>,<condition>,...}


Condition variable is always either relevant to the request itself or relevant on the resource which is being accessed. This comes always with a prefix of a request or a target.

You want to limit your group admins to be able to modify anything but the Admin group (example from Online documentation):

Allow group GroupAdmins to use users in tenancy where != 

And if you want to limit group access on specific resource-types you can use(example from Online documentation):

Allow group XYZ to manage groups in tenancy
 where any {request.permission='GROUP_INSPECT',

Normally when you grant the manage groups permission it has one additional individual resource-type: GROUP_DELETE. Now when you limit the permission not to include it then group XYZ does not have access to delete groups.

In conditions you can also use either string with single-quotes or a pattern match. Pattern match is used as /*OCI*/, /*OCI/ or /OCI*/.

Example case

Now for a typical case what might come up. Company has two or more different applications and you don’t want users to access other resources apart from what they should. In my case I will have Oracle EBS and Oracle BI as example applications. I want to have network admin group which manages networks in all compartments and admin groups for each different compartments with specific policies.

In addition I will have group for user management. Each group will have one user assigned.

Compartment example
This is what compartment, group and user layout will look like

Group policies what I will give are as follows.

For Network-Admins:

Allow group Network-Admins to manage virtual-network-family in tenancy

For User-Management:

Allow group User-Management to manage users in tenancy
Allow group User-Management to manage groups in tenancy

For Oracle-BI-Admin:

Allow group Oracle-BI-Admin to manage instance-family in 
compartment Oracle-BI

Allow group Oracle-BI-Admin to use virtual-network-family in 
compartment Oracle-BI

Allow group Oracle-BI-Admin to manage volume-family in 
compartment Oracle-BI

Allow group Oracle-BI-Admin to manage database-family in 
compartment Oracle-BI

For Oracle-EBS-Admin:

Allow group Oracle-EBS-Admin to manage instance-family in 
compartment Oracle-EBS

Allow group Oracle-EBS-Admin to use virtual-network-family in 
compartment Oracle-EBS

Allow group Oracle-EBS-Admin to manage volume-family in 
compartment Oracle-EBS

Allow group Oracle-EBS-Admin to manage database-family in
compartment Oracle-EBS

Allow group Oracle-EBS-Admin to manage file-family in
compartment Oracle-EBS

I’m giving quite wide permissions for both admins here. If you would like to you could always limit the access so users can’t create volumes but just use existing one for attaching etc. It depends on your use case!

Also both compartment admins get access to manage database-family. It might be that for some reason you wouldn’t want to create database through service but install it yourself. Then you wouldn’t need the database-family policy.

Group Oracle-EBS-Admin gets also policy to manage file-family. This is really good service for EBS customers as you can create the shared APPL_TOP mounts through file-family and then just mount on servers where you need to have it.

Deploying the compartment layout with Terraform

If you want you can always create everything manually with OCI. But I want to use automation and infrastructure as code so I will use Terraform to deploy these to my OCI tenancy.

With Terraform I have four different modules in their respective folders and one main folder which calls these reusable modules. Main folder has also variables defined specific for this case. So as an example when I create users I call the same module like this:


Few important things to remember. When you make policy statement variables they are passed as a list. So the variable will look like:


Remember to define type as a list in the module:

variable “statements” {type = “list”}


Also right now the modules I have are: “create users”, “create compartments”, “create groups with policy” and “create group relationship with user”.

I used to have separate modules for groups and policies but noticed that sometimes Terrafrom dependency didn’t work as expected even though I was passing some variables from group module to policy module.

To my understanding Terraform should then wait the referenced resource to be created first. To have a workaround I put both resources under same module which was a good and simple solution in this case.

Now I will just run familiar Terraform commands terraform init, terraform plan and finally terraform apply.

My example created total of 22 OCI resources:

Apply complete! Resources: 22 added, 0 changed, 0 destroyed.

And if I just check from the console I can see everything is there as required. Below you can see compartments and policies created.



In this post I wanted to show how easy it is to deploy base layout for your operations. You can start small and then think what kind of policies are required for users and how to have them setup in a most efficient way.

Unless there is a real requirement I wouldn’t want to make whole setup too complicated. On the other hand giving a wide access to all users could create some other issues in your setup.

Oracle Cloud Infrastructure Architect certification review

Earlier this week I took the Oracle Cloud Infrastructure (OCI) Architect Associate certification 1Z0-932 which I had been studying for few months while using OCI at the same time. Glad to say I passed it with good score to leave me happy on the effort!

As the certification is quite new there isn’t a lot out there what you should focus on while studying. Oracle has put out the exam topics here. As you can see it lists most of the services related to OCI and how you should be familiar with those.

In my study I wanted to focus on few specific areas – getting idea on how the overall network services work with OCI , Database service and also Identity and Access Managent (IAM). Now after the test I’d say this was a good approach. You really should know the network components inside out and get idea on how to build high availability solutions. Understand what difference you have with public and private subnets and how you can access different components from subnets and how you setup load balancers for high availability.

On the Database service you want to understand what types of service you can use and how the backups and restores are handled. Databases are still Oracle’s core offering so obviously a lot of questions are from this area.

With IAM once you get the concept of policies, compartments and groups learning it comes quite easy. This is different compared to AWS IAM and is one of the best features of OCI so learn it!

I had also gotten quite familiar with Terraform and this was reoccurring topic in the exam. It’s good Oracle tries to get you focusing on infrastructure as code and automation as that should be at least considered in each OCI project as a possibility.

Each OCI service was covered in the exam so remember to study all of them! During studying I used the youtube videos of OCI fundamentals by Oracle and read the Online Documentation few times through.

I’d suggest also creating free account for OCI which gives you 30 days time to test things while you are studying. Using the services and console you get the idea how you can create resources and what is needed when you create each resource.

If I compare OCI and AWS Solution Architect certifications then AWS understandably has wider range of services what you need to learn but both concentrate on similar topics so studying for one gives you a good basis for another! AWS certifications I did earlier this year definitely helped on the network concepts.

Hope this helps you on your study!

Using Oracle Cloud Infrastructure with Terraform modules

When I started to use Terraform I quickly adapted on concept of using modules with it. Why?

Because in my opinion the more you can automate and standardize the easier overall management of your solution becomes.

So what are modules and why should I use them? I made a short video presentation on creating infrastructure using Terraform modules. If you have time to check it it’s right here!


Leave me some feedback if you liked the video and if you would like to see similar videos on Terraform or OCI in the future.

As promised some links which I mention in my presentation:

Oracle Cloud Infrastructure documentation:

Oracle Cloud Infrastructure fundamentals playlist(this is really good!):

Terraform and downloads:

Terraform OCI provider:

Terraform OCI documentation (again really useful!):

Accenture OCI performance study:

Getting to know basics of Oracle Cloud Infrastructure Load Balancing service

Feels like I’m jumping bit from topic to another but I had some testing ongoing with OCI Load Balancing service so thought to write a post on it. I’ll also throw few comparisons with AWS ELB so it gives an idea how Oracle has done it’s service.

In OCI the Load Balancing (LB) service is a regional service either public or private in your Virtual Cloud Network (VCN) depending what requirement you have for it and it manages either TCP or HTTP traffic.

If you need a public LB then the service will create two LB’s (primary and standby) in different Availability Domains (AD) so it will provide high availability. This means you will need to provide two different subnets from your VCN for the LB and they will get two different private IP’s. You can’t determine which one will be the primary.

The LB will also be assigned a floating public IP so in case one of the AD’s would go down the IP would be transferred to another LB where as in AWS the DNS name is linked to ELB IP addresses.

If you instead need a private LB this will be configured in one AD only and there will be a floating private IP assigned to the LB. If the AD would go down there is no fall back to another AD so that specific LB would go down.

Creating Load Balancer

For my test I created a public LB with three backend servers servicing http content from port 80. I used three servers so I could demonstrate some of the health check functionality Oracle has implemented.

Creating the Load Balancer screen

In above LB creation screen you can see when I select to create a public LB I need to choose two public subnets which reside in different availability domains. These I had created beforehand.

Also when you create the LB you need to select correct shape for it. The options are 100Mbps, 400Mbps and 8Gbps so there is plenty of variety depending on your requirement and how much you are willing to pay!

The other components which are required for LB are:
Backend Set which is grouping of backend servers and health check and load balancing policy defined
Backend Servers which are the destination of your LB routing
Listener to listen incoming traffic on specific port and route it to a backend set

Like said in the backend set definition you have a health check policy. You will define what path your healthcheck URL (for example /health.html in case of http) resides and in which interval it is polled on. Also things like status code of response are defined here.

You will also define the load balancing policy, whether its round robin, IP hash or least connection. All these are described in the documentation.

Next when you define the actual backend servers with ports is my biggest UI related gripe in the whole service. You either need to know the OCID or IP of the server and you have a nice link to view the instances. But you can’t copy the IP or OCID from anywhere! This could be so easy if you could just have a drop down  where to choose the servers from.

Type the OCID which you have of course saved beforehand to a text file here.

Good help when creating the backend servers is the checkbox to help system create proper security list rules so your servers can be accessed.

Once you have added servers you will have in your backend set you can continue on to the listener. In the listener you will define port it is listening on and related backend set. If you need to you can optionally create a path route set which tells listener which request is routed to which server.

That’s it almost! You still need to edit your security lists so traffic is allowed to backend servers (if you didn’t do this already when creating the backend servers) and that the traffic is allowed to and from to the listener. This is what documentation says about security lists:

To enable backend traffic, your backend server subnets must have appropriate ingress and egress rules in their security lists. When you add backend servers to a backend set, the Load Balancing service Console can suggest rules for you, or you can create your own rules using the Networking service.

And for the listener be sure to checkout this.

Everything has been setup and health of servers is OK

Each part of LB service has different health categories but I wanted to look more on backend set during this test.  The health is divided into following categories OK, WARNING, CRITICAL, UNKNOWN.

If everything checks out fine then status is OK.

If more than one but less than half shows up as CRITICAL, WARNING or UNKNOWN then status is WARNING.

If more than half shows up as CRITICAL, WARNING or UNKNOWN then status is CRITICAL.

And if following conditions are met then status is UNKNOWN: More than half shows up as UNKNOWN, listener is not properly configured or the system could not retrieve metrics.

All this is explained here.

Example when health changes to WARNING when less than half of servers are CRITICAL


If I would say anything about creation of OCI LB compared to AWS ELB then creating ELB has similar components but the AWS UI is bit more user friendly.

With OCI you need to know all components you have to update where as in AWS you go one step forward all the time. All this goes away if you use some orchestration tool of course.

You should also consider how many actual load balancers you need vs if you create one load balancer with multiple listeners. You can also use SSL certificates with your LB and terminate the connection to the LB, use backend SSL or use end-to-end SSL based on your requirements.

Finally link to OCI Load Balancing Service documentation.

OCI network with public and private subnets

When you create your VCN (Virtual Cloud Network) in Oracle Cloud Infrastructure so that you have a virtual network for your compute servers you then create subnets under the VCN. The subnets will contain part of CIDR block you have allocated for the VCN.

If you are not familiar with VCN then good place to start is from VCN FAQ:

So for example your VCN is CIDR block of (65,536 ip’s) and then you create a subnet under it with (256 ip’s) . Oracle reserves two first IP’s and the last IP from each subnet on their use.

Either you will have instances which are faced against public internet or then you want to keep your instances private so only you can access them for example through your corporate network.

What do I need for my subnets?

If you need to create both public and private instances then you should create respective subnets. One subnet can be accessed from the internet and other one can not.

For the subnet which is public you can then allocate a public IP address to your server (or  actually for interface of it). The server will need public IP, a security list rule which allows traffic to specific ports and an Internet gateway which is mapped to the route table assigned to the public subnet.

For the private subnet we don’t need to add public IP or Internet gateway in the route table. In fact when you create a subnet and you choose private subnet it won’t allocate public IP addresses to that subnet.


With OCI you don’t need to add VCN’s CIDR block in the route table but instead if security lists allow then servers which belong to subnets in the same VCN have automatically a route between each other. This is  different compared to AWS!

Below image shows that I have used the default VCN route table for my subnets and it has the Internet gateway assigned for it.


If I don’t specify a route table when creating a subnet it will allocate the default route table to it. You can’t change the route table in the subnet anymore after that to another one! However you can modify the existing route table routes.

So if you share the route table between multiple subnets this could become an issue!

Now I have two subnets – public and private. Both have a default route table assigned which has a route to Internet gateway. I also have a security list which allows SSH traffic inside my subnets.

If I would like to access my private or public subnet from corporate network I would need to add a route to dynamic routing gateway (DRG) which would have VPN tunnel to my coroorcor network.

Accessing your subnets

I have also two VM’s – one in public (public1) and one in private(private1).

Two instances in different subnets

As you can see the other one has public IP address and the other one not and they belong to different subnets.



During VM creation I have created a SSH key which I will use to access my public and private VM. When logging in I will use my default VM user opc and supply the private key file I have created.

[simo@mylinux ~] ssh opc@ -i s1.ppk
Last login: Wed Feb 14 09:54:46 2018 from
[opc@public1 ~]$

That’s it – so I can access my public VM fine. Now if I would need to access my private VM I can use my public VM as a jump server.

This is something you will need to think when creating your network. What is the method accessing your private subnets and how will they access the internet (to download packages etc). Jump servers and NAT gateways are an option in these cases.

As I mentioned earlier subnets within VCN don’t need a route with each other so I should be able to access my private VM from my public VM without modifications to the route table. Let’s test!

[opc@public1 ~]$ ssh opc@ -i s1.ppk
Last login: Wed Feb 14 10:12:44 2018 from
[opc@private1 ~]$

Works smoothly! So to summarize you need to understand which servers you will place in public and which in private subnet. Also think of NAT gateways to access internet from your private subnet. In my example even though I have the same Internet gateway assigned to both subnets I can’t access internet from my private VM.

Oracle doesn’t have NAT gateway as a service yet but instead you need to create your own NAT instance in public subnet and route private subnet traffic through that NAT instance to internet.

Good example on deploying NAT instance with Terraform:

After playing around I will want to remove my subnets so they aren’t left there as they have no further use. Remember that subnets must be empty before deleting them!