Hosting a Dockerized Node.js App on Oracle Cloud Free Tier

Published At: February 15, 2026

Reading Time: 4 minutes

Author: Prajwol Shrestha

I recently decided to host one of my Dockerized Node.js apps on a VPS using Oracle Cloud Free Tier - not just to deploy it, but to understand what’s actually happening under the hood.

I wanted to learn:

  • How to create and configure a compute instance
  • How Docker behaves on a VPS
  • Why backend ports shouldn't be exposed directly
  • How NGINX fits into the picture
  • What restart policies actually do

Here's exactly how I set it up - and what I learned along the way.


Step 1 - Create a Compute Instance on Oracle Cloud

Inside Oracle Cloud:

  1. Go to Compute -> Instances
  2. Click Create Instance
  3. Choose Ubuntu (I used 22.04)
  4. Select an Always Free eligible shape (1 vCPU, ~1GB RAM)

When creating the instance, upload your SSH public key or download the key Oracle provides.

Once the instance is running:

1ssh -i your-key.pem ubuntu@YOUR_SERVER_IP

Now you're inside a real Linux server - not a managed environment.


Step 2 - Basic Setup & Firewall

First, update the system:

1sudo apt update 2sudo apt upgrade -y

Then enable UFW:

1sudo apt install ufw -y 2sudo ufw allow OpenSSH 3sudo ufw enable

At this point:

  • Only SSH is allowed
  • Everything else is blocked

Important lesson:

Oracle Cloud has its own Security List (cloud firewall).
Ubuntu has UFW (OS firewall).

If something doesn't work later, check both.


Step 3 - Install Docker

Install Docker

1curl -fsSL https://get.docker.com -o get-docker.sh 2sudo sh get-docker.sh

After installing Docker, I added my user to the docker group:

1sudo usermod -aG docker $USER 2exit

Reconnect and test:

1docker run hello-world

Docker is now ready to run containers.


Step 4 - Create a Private Docker Network

Before running anything, I created a custom Docker network:

1docker network create web

This was important.

Instead of exposing my Node.js app like this:

1docker run -p 3000:3000 my-app

I wanted the app to stay internal and let NGINX handle public traffic.

Containers attached to the same custom network:

  • Can talk to each other using their names
  • Are not exposed publicly unless you publish ports

Think of it as a private network inside your VPS.


Step 5 - Install NGINX Reverse Proxy

At this point, the Node.js app is internal. But something still needs to handle incoming HTTP requests from the outside world. That’s where NGINX comes in.

I used NGINX Proxy Manager for simplicity.

Create a directory for it in VPS:

1mkdir nginx-proxy 2cd nginx-proxy

Then create docker-compose.yml

Here's the simplified docker-compose.yml:

1version: "3.8" 2 3services: 4 npm: 5 image: jc21/nginx-proxy-manager:latest 6 container_name: nginx-proxy 7 restart: unless-stopped 8 ports: 9 - "80:80" 10 - "443:443" 11 - "81:81" 12 volumes: 13 - ./data:/data 14 - ./letsencrypt:/etc/letsencrypt 15 networks: 16 - web 17 18networks: 19 web: 20 external: true

networks: web

This attaches NGINX to the same private network as the app.
So it can forward traffic internally.

volumes

This ensures configuration and SSL certificates survive container restarts.

restart: unless-stopped

It means:

  • If the VPS reboots -> the container starts automatically
  • If Docker restarts -> container comes back
  • If it crashes -> it restarts
  • If you manually stop it -> it stays stopped

Then start it:

1docker compose up -d

Now NGINX is running.


Step 6 - Open Ports in Oracle Cloud

Open ports in OS level

1sudo ufw allow 80 2sudo ufw allow 443

Even after configuring UFW, I couldn't access the server from the browser.

That's because Oracle Cloud blocks traffic by default.

I had to allow:

  • Port 80
  • Port 443

Inside:

Networking - VCN -> Security List

This reinforced something important:

Cloud firewall and OS firewall are separate layers.


Step 7 - Deploy the Dockerized Node.js App

I pushed my Node.js image to a container registry (I used Docker Hub).

On the VPS:

1docker login 2docker pull myusername/my-node-app:latest

Then created a folder:

1mkdir app 2cd app

Created .env:

1NODE_ENV=production 2PORT=3000

Then docker-compose.yml:

1version: "3.8" 2 3services: 4 node-app: 5 image: myusername/my-node-app:latest 6 container_name: node-app 7 restart: unless-stopped 8 env_file: 9 - .env 10 networks: 11 - web 12 13networks: 14 web: 15 external: true

Notice something important:

There is no -p 3000:3000.

The Node.js app is running - but only inside the Docker network.

It is not publicly accessible.

Start it:

1docker compose up -d

Step 8 - Connect NGINX to the Node.js App

Inside NGINX Proxy Manager:

  1. Add Proxy Host
  2. Forward Hostname: node-app
  3. Forward Port: 3000
  4. Enable SSL (if using a domain)

Now traffic flows like this:

Browser -> NGINX -> Node.js container

Port 3000 remains internal.

This separation reduces your public attack surface. Only NGINX is exposed. Your application server stays internal.


Free Tier Reality

Oracle Free Tier gives ~1GB RAM.

You can monitor usage:

1htop 2docker stats

This comfortably runs:

  • NGINX
  • One or two small Node.js apps

What I Learned From This

Setting this up myself helped me understand:

  • What a compute instance actually is
  • Why reverse proxies are important
  • How Docker networking isolates services
  • Why backend ports shouldn't be exposed
  • How restart policies affect uptime
  • How cloud and OS firewalls interact

It's very different from just deploying code.

You start understanding the system your app runs on.

And that's what made this exercise worth it.

#docker

#cloud

#nginx

#vps