A few weeks ago, there was an active discussion on [HN](http://news.ycombinator.com/item?id=26239711) about the [Free For Dev](https://free-for.dev/) page which compile a list of free tier (or at least free _trial_) resources that are useful for developers. The page mentioned Oracle Cloud provides two _always free_ VMs ([specification](https://docs.oracle.com/en-us/iaas/Content/FreeTier/resourceref.htm#ariaid-title2)), whereas big three only offer one VM for a year. While I knew about this offering for a while, but I've always been sceptical about it.
I decided to give it a try after a few comments mentioned the offering is genuine. Besides, I always felt a bit uneasy about my _single_ reverse proxy setup. The sign up process was slightly bumpy because the payment processing page (shop.oracle.com) requires `Referer` header that I blocked using [Privacy Possum](https://github.com/cowlicks/privacypossum); I managed to complete the sign up after disabling the addon on the page.
For cloud image, NixOS only officially supports AWS EC2, so I expected the installation to be manual. I searched Oracle's official documentation and found these two articles ([1](https://blogs.oracle.com/cloud-infrastructure/importing-virtualbox-virtual-machines-into-oracle-cloud-infrastructure), [2](https://docs.oracle.com/en-us/iaas/Content/Compute/Tasks/importingcustomimagelinux.htm)). The first article mentioned custom image can be created with VirtualBox, with notable requirements that the disk image is in KVM-compatible VMDK/QCOW format and the bootloader is in BIOS (not UEFI) mode. Long story short, that didn't work. I made sure Qemu guest agent and `virtio` kernel module are installed, but to no avail. Maybe I forgot to set the interface name to `ens3`?
``` nix
networking.useDHCP = false;
networking.interfaces.ens3.useDHCP = true;
```
Undeterred by this initial setback, I searched for "nixos oracle cloud" and found this [gist](https://gist.github.com/misuzu/89fb064a2cc09c6a75dc9833bb3995bf). It offered two approaches, the first one is installing Nix package manager on an Ubuntu VM (provisioned using an official image) and _partially_ replace the OS by overwriting the bootloader so it boots NixOS instead. But that didn't work too, so I tried the second one instead, which _worked_. So, let's get started.
Edit (15 Mar 2021): I stumbled upon [NixOS-Infect](https://github.com/elitak/nixos-infect), an installation script similar to the first approach of that gist. The script seems popular and may work better, although no one has tried it on Oracle Cloud yet.
The working approach involved building a [kexec](https://en.wikipedia.org/wiki/Kexec) image using Nix/NixOS. If you're not using NixOS, the NixOS' [VirtualBox image](https://nixos.org/download.html#nixos-virtualbox) works too. If you're building inside a VM (of your local workstation, not Oracle Cloud), I recommend provisioning at least two CPUs as the operation is quite intensive.
Create the following *.nix file and _only_ add your SSH key, do not modify other lines (especially `environment.systemPackages`) or it may not boot.
nix-build '<nixpkgs/nixos>' -A config.system.build.kexec_tarball -I nixos-config=./kexec.nix
```
The build will create `result/tarball/nixos-system-x86_64-linux.tar.xz` compressed kexec tarball. It took around 15 minutes for me. While waiting for the build to complete, let's create an Ubuntu instance.
## Create IAM policy
> Skip this step if you prefer to use admin account
I created a separate user with just enough permission to manage instances, VCN and boot volume backup.
```
Allow group InstanceLaunchers to manage instance-family in compartment ABC
Allow group InstanceLaunchers to read app-catalog-listing in compartment ABC
Allow group InstanceLaunchers to manage volume-family in compartment ABC
Allow group InstanceLaunchers to manage virtual-network-family in compartment ABC
```
## Create a new VCN
> Skip this step if you're not going to use custom SSH port nor Mosh
Prior to launching a new instance, I created a VCN using VCN Wizard with a public and a private subnets. I created a security list (equivalent to NACL in AWS) and a network security group (equivalent to security group in AWS) to allow ingress/incoming custom SSH port and [Mosh](https://mosh.org/) (UDP 60000-61000), detached the default security list from the public subnet and attached the newly created one. I also created a reserved IP.
## Launch an Ubuntu instance
Launch a new instance with the following properties:
- Image: Ubuntu 20.04.1 minimal (any version >= 18.04 is fine)
- Shape: Micro.VM
- VCN: Create new or choose the VCN created in previous section
- Subnet: Public
- Network security group: _optional, depends on your VCN_
- Public IP: none (we'll assign the reserved IP later)
- SSH: Upload/paste your SSH public key here
`iptables` is enabled by default in Oracle-provided image which only allow incoming SSH (TCP 22). If you prefer to use custom SSH port or want to use Mosh, you need to open the ports using cloud-init script (under Advanced Settings).
``` sh
#!/bin/sh
iptables -I INPUT 1 -p tcp --dport 1234 -j ACCEPT
sed -i 's/^#Port 22$/Port 1234/' "/etc/ssh/sshd_config"
Alternatively, you could also upload it to the Object Storage and then download it from the instance using Pre-Authenticated Request or Dynamic Groups IAM policy. The image is 300MB and it's more reliable to upload using OCI CLI. OCI CLI splits large file into 100MB chunks (adjustable) for more reliable upload.
Note that multipart upload requires `OBJECT_OVERWRITE` permission which is not included in the [Common Policy](https://docs.oracle.com/en-us/iaas/Content/Identity/Concepts/commonpolicies.htm#ariaid-title25) example. IAM policy below enables multipart upload.
```
Allow group ObjectWriters to read buckets in compartment ABC
Allow group ObjectWriters to manage objects in compartment ABC where all {target.bucket.name='BucketA', any {request.permission='OBJECT_READ', request.permission='OBJECT_CREATE', request.permission='OBJECT_INSPECT', request.permission='OBJECT_OVERWRITE'}}
```
The above permission doesn't include permission to create bucket, I'm assuming the bucket already created beforehand. `PAR_MANAGE` permission is required on both bucket and objects if you want to create pre-authenticated request. Once the policy is in place, upload using OCI CLI:
```
VM_DIR="/home/<username>/result/tarball"
VM_FILE="nixos-system-x86_64-linux.tar.xz"
# Namespace value is available on bucket details
NAMESPACE="xxx"
BUCKET_NAME="BucketA"
oci os object put -ns "${NAMESPACE}" -bn "${BUCKET_NAME}" --file "{VM_DIR}/${VM_FILE}"
```
## Execute kexec iamge
Once the instance has the kexec tarball, uncompress and execute it:
```
cd / && sudo tar xf /tmp/nixos-system-x86_64-linux.tar.xz && sudo /kexec_nixos
# wait until "executing kernel, filesystems will be improperly umounted" message is shown
```
This launches a separate root shell. In your local machine, launch another shell and ssh into the kexec:
# set hostname, add users and ssh-keys, enable openssh/mosh
nano /mnt/etc/nixos/configuration.nix
nixos-install --no-root-passwd
shutdown -r now
```
### Launch a redundant instance
Since Oracle offers two VMs, might as well take it. The second instance can be in the same VCN and subnet, but I put it in another VCN for network isolation (or just because I _can_). I also created another reserved IP for the second instance.
* (Update 5 April 2021): Boot volume created from backup somehow is not free, it charges [*performance units*](https://www.oracle.com/cloud/price-list.html#storage); you may be able to avoid this cost by cloning the boot volume instead. Alternatively, you can always create the volume from scratch.
3. Launch a new instance using the second boot volume
4. Attach reserved IP to the instance
> It's recommended to launch instances in a separate fault domains to isolate server rack failure.
Add the second instance's IP to your DNS record, you now have [round-robin DNS](https://en.wikipedia.org/wiki/Round-robin_DNS) redundancy.
## Conclusion
The overall process was similar to mounting an ISO on a cloud instance and install from there, which I did in my previous VPS host. Although I would've preferred using the _official_ method of importing a KVM image, but that involves uploading 2GB disk image. The kexec tarball approach only requires uploading a 300MB file. Maybe I'll try creating a [QCOW image](https://github.com/nix-community/nixos-generators/blob/master/formats/qcow.nix) next time.
Summary of Oracle Cloud always free resource:
Pros:
- 2 VMs
- 20TB egress
- 50GB volume/instance
Limitations:
- 50Mbps Internet
- One region (though you can choose any region during sign up)