DEV Community

Prajwol Shrestha
Prajwol Shrestha

Posted on

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

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:

ssh -i your-key.pem ubuntu@YOUR_SERVER_IP
Enter fullscreen mode Exit fullscreen mode

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


Step 2 - Basic Setup & Firewall

First, update the system:

sudo apt update
sudo apt upgrade -y
Enter fullscreen mode Exit fullscreen mode

Then enable UFW:

sudo apt install ufw -y
sudo ufw allow OpenSSH
sudo ufw enable
Enter fullscreen mode Exit fullscreen mode

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

curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
Enter fullscreen mode Exit fullscreen mode

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

sudo usermod -aG docker $USER
exit
Enter fullscreen mode Exit fullscreen mode

Reconnect and test:

docker run hello-world
Enter fullscreen mode Exit fullscreen mode

Docker is now ready to run containers.


Step 4 - Create a Private Docker Network

Before running anything, I created a custom Docker network:

docker network create web
Enter fullscreen mode Exit fullscreen mode

This was important.

Instead of exposing my Node.js app like this:

docker run -p 3000:3000 my-app
Enter fullscreen mode Exit fullscreen mode

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:

mkdir nginx-proxy
cd nginx-proxy   
Enter fullscreen mode Exit fullscreen mode

Then create docker-compose.yml

Here's the simplified docker-compose.yml:

version: "3.8"

services:
  npm:
    image: jc21/nginx-proxy-manager:latest
    container_name: nginx-proxy
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
      - "81:81"
    volumes:
      - ./data:/data
      - ./letsencrypt:/etc/letsencrypt
    networks:
      - web

networks:
  web:
    external: true
Enter fullscreen mode Exit fullscreen mode

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:

docker compose up -d
Enter fullscreen mode Exit fullscreen mode

Now NGINX is running.


Step 6 - Open Ports in Oracle Cloud

Open ports in OS level

sudo ufw allow 80
sudo ufw allow 443
Enter fullscreen mode Exit fullscreen mode

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:

docker login
docker pull myusername/my-node-app:latest
Enter fullscreen mode Exit fullscreen mode

Then created a folder:

mkdir app
cd app
Enter fullscreen mode Exit fullscreen mode

Created .env:

NODE_ENV=production
PORT=3000
Enter fullscreen mode Exit fullscreen mode

Then docker-compose.yml:

version: "3.8"

services:
  node-app:
    image: myusername/my-node-app:latest
    container_name: node-app
    restart: unless-stopped
    env_file:
      - .env
    networks:
      - web

networks:
  web:
    external: true
Enter fullscreen mode Exit fullscreen mode

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:

docker compose up -d
Enter fullscreen mode Exit fullscreen mode

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:

htop
docker stats
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (0)