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:
- Go to Compute -> Instances
- Click Create Instance
- Choose Ubuntu (I used 22.04)
- 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:
- Add Proxy Host
- Forward Hostname:
node-app - Forward Port:
3000 - 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