If someone needs to build a blog, e-commerce or corporate landing page, I would always explore Wordpress as a solution before recommending other options. This is simply because Wordpress is established and battle tested for most of these use cases. On the other hand, if a fully customizable web and/or mobile application is required, then a headless CMS is great to use with any frontend framework of choice.
Given the growing diversity of frontend delivery methods, headless CMS is a natural movement towards these emerging trends. This post will focus on how to set up Strapi on Oracle Cloud Infrastructure for free.
Strapi is an open source Node.js headless CMS. Their community version is forever free. You get all benefits except of single sign on feature. It also has customizable admin panel, growing numbers of plugins and most importantly a vibrant community. It is great for developers who are familiar with Node.js. Strapi supports both sql databases like Maria, Postgres and MySql and nosql like Mongodb. Under the hood, ORM bookshelf.js and ODM mongoose did the heavy lifting. This is quite an important point because while bookshelf.js supports Oracle thanks to Knex, the Strapi maintainer is not adding support to OracleDB anytime soon (see this issue). Had this support been enabled, we can tap on Oracle autonomous databases (Free!) that handles replication, scaling, failover and many more. Anyway you can still find many alternative hosting providers guides here. In this guide, I am going to cover a less explored option - Oracle Cloud Infrastructure for hosting Strapi application.
Oracle Cloud Infrastructure (OCI)
Honestly, I didn’t know OCI existed until recently when I learnt about their forever free VM instances. If you ran out of your Google Cloud credit or your AWS account no longer has free tier, then OCI’s always free instances should be exciting to you. These instances are great for experimenting ideas such as the topic in this post.
Let’s get started
When you sign up with OCI, you get two always free instance. We will use one instance for PostgreSQL DB and the other instance for Strapi backend.
Before we begin, let’s look at Strapi deployment methods. You can clone the source code and set up everything manually or deploy through docker image. Note that you need to decide which database to use. I will use PostgreSQL because I had a ready-to-use Ansible playbook that setup and configure a PostgreSQL (IaaC for the win).
Install a PostgreSQL database
In side panel, click on Compute/Instances
Click Create Instance in compute panel
Change its name to PostgreSQL
Click edit in Image and shape section and Change Image to Canonical Ubuntu Always Free Eligible 20.04 Minimal 2021.06.14-0
Create SSH key pairs using this guide. If using rsa, you should upload or paste your public key. Lastly click Create
Wait for instance provision to complete. Once completed,
- take note of the assigned public IP
- click on Subnet
- click the default security list in Security List section.
- click Add Ingression Rules.
- key in
- 0.0.0.0/0 for SOURCE CIDR.
- 5432 for DESTINATION PORT RANGE
- PostgreSQL port for DESCRIPTION (optional).
- click Add Ingression Rules
You can use the following ansible playbook I prepared to setup PostgreSQL or
you can use it as a reference and manually set up the database. There are also plethora of online resources to help you.
- hosts: pgsql name: setup and configure postgresql-13 become: true gather_facts: no pre_tasks: - name: install pg 13 script: /path/to/your/install_pg13.sh - apt: name: pip state: present - name: Make sure psycopg2 is installed pip: name: psycopg2 state: present tasks: - name: Temporarily trust user postgres community.postgresql.postgresql_pg_hba: dest: /etc/postgresql/13/main/pg_hba.conf contype: local users: postgres databases: all method: trust state: present - name: Create db postgresql_db: login_user: postgres name: strapi - name: Create user and grant permission community.postgresql.postgresql_user: db: strapi name: strapi-user password: "secret123" priv: "CONNECT/ALL" - name: Change db owner community.postgresql.postgresql_owner: db: strapi new_owner: strapi-user - name: Enable client in host-based authentication (HBA) community.postgresql.postgresql_pg_hba: dest: /etc/postgresql/13/main/pg_hba.conf contype: host users: strapi-user source: 0.0.0.0/0 databases: strapi method: md5 state: present - name: Revoke trust from user postgres community.postgresql.postgresql_pg_hba: dest: /etc/postgresql/13/main/pg_hba.conf contype: local users: postgres databases: all method: trust state: absent - name: Accept connection from to all network interfaces lineinfile: path: /etc/postgresql/13/main/postgresql.conf regexp: '# listen_addresses' line: "listen_addresses = '*'" - name: Restart postgresql-13 service command: cmd: service postgresql restart register: result - name: Notify debug: var: result verbosity: 2 # This is where I got stuck for a good 1 hour # adding the port to security list of subnet is not enough. # Any port need to be added to the iptables before the last rule as well. # I wish Oracle document this point more conspicuously # Here is the SO thread that pointed me to the right direction. # https://stackoverflow.com/questions/5479421 opening-port-80-on-oracle-cloud-infrastructure-compute-node - name: Allow connections on TCP port 5432 ansible.builtin.iptables: chain: INPUT protocol: tcp destination_port: 5432 jump: ACCEPT action: insert # the last rule reject everything so we need to add before it rule_num: 6 comment: Accept new postgres connections.
#! /bin/bash # Create the file repository configuration: sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list' # Import the repository signing key: wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add - # Update the package lists: sudo apt-get update # Install the latest version of PostgreSQL 13. sudo apt-get -y install postgresql-13
- Once all set, use PgAdmin4 to test your connection.
- On second instance, repeat what you did from step 1 - 7 but name your instance to strapi and add relevant description. Also open port 80 instead of 5432
- Create a docker-compose.yml and fill in your database details
.... version: "3" services: strapi: image: strapi/strapi environment: DATABASE_CLIENT: postgres DATABASE_NAME: strapi DATABASE_HOST: [YOUR_DB_HOST] DATABASE_PORT: 5432 DATABASE_USERNAME: strapi-user DATABASE_PASSWORD: [YOUR_DB_PASSWORD] volumes: - ./app:/srv/app ports: - "80:1337" ....
- hosts: strapi name: setup and configure strapi become: true gather_facts: no pre_tasks: - apt: name: pip state: present - name: Make sure docker sdk is installed pip: name: docker state: present - name: Make sure docker-compose is installed pip: name: docker-compose state: present - name: Copy file with owner and permissions ansible.builtin.copy: src: /your/path/to/docker-compose.yml dest: /home/ubuntu/docker-compose.yml owner: ubuntu mode: '0644' tasks: - name: Tear down existing services become: true community.docker.docker_compose: project_src: "." state: absent - name: Create and start services community.docker.docker_compose: project_src: "." register: output - ansible.builtin.debug: var: output
- docker-compose up -d and you should be able to visit the page via your public ip
Enable SSL/TLS (optional)
This is optional if you are not going production. To enable SSL/TLS, in your docker-compose.yml add after strapi the following:
nginx: image: nginx:1.15-alpine ports: - "80:80" - "443:443" volumes: - ./data/nginx:/etc/nginx/conf.d - ./data/certbot/conf:/etc/letsencrypt - ./data/certbot/www:/var/www/certbot certbot: image: certbot/certbot volumes: - ./data/certbot/conf:/etc/letsencrypt - ./data/certbot/www:/var/www/certbot
TODO: Update the Ansible playbook.