DEV Community

Prajwol Shrestha
Prajwol Shrestha

Posted on

How to Dockerize and Deploy a NestJS App on Render for Free

So, you’ve built your NestJS app and now want to deploy it online, without spending any money or adding a credit card. In this blog, we’ll walk through a clear, step-by-step process to Dockerize your app and deploy it on Render for free.

Render’s free tier makes it easy to deploy Dockerized apps, while Docker ensures your project runs consistently everywhere. In this guide, we’ll:

  • 🐳 Dockerize a NestJS app using pnpm.
  • 🚀 Deploy it to Render in two different ways.
  • 🔁 Add CI/CD for automatic deployments on every push to main.
  • 🎁 Bonus: Keep your free Render app alive 24/7 using a Cloudflare Worker.

Let’s dive in! 🐳


Why Docker for NestJS?

Docker helps package your NestJS app with all its dependencies so it runs exactly the same in all environments.

Render supports two ways of deploying Docker apps:

  • From a Dockerfile → Render builds the image itself
  • From a prebuilt Docker image → You push your image to Docker Hub (or any registry), and Render pulls it

We’ll cover both options.


Step 1: Dockerizing the NestJS App

First, we need to containerize our application. If you don’t have Docker installed, grab it here.

Create the Dockerfile

Create a file named Dockerfile in your project's root directory:

Here’s a example Dockerfile using pnpm:

# Use a lightweight Node.js base image
FROM node:20-slim 

# Set working directory
WORKDIR /app

# Copy dependency files first (better cache usage)
COPY package.json pnpm-lock.yaml* ./

# Install dependencies
RUN npm install -g pnpm && pnpm install --frozen-lockfile

# Copy the rest of the source code
COPY . .

# Build the NestJS project
RUN pnpm run build

# Expose the app’s port
EXPOSE 3000

# Start the app
CMD ["pnpm", "run", "start:prod"]

Enter fullscreen mode Exit fullscreen mode

Don't Forget the .dockerignore

Create a .dockerignore file to keep your image lean and mean by excluding unnecessary files:

node_modules
dist
.git
Dockerfile
.dockerignore
.env
Enter fullscreen mode Exit fullscreen mode

Test It Locally First

Before we deploy, let's build and run the container locally to make sure everything works.

docker build -t nest-backend .
docker run -p 3000:3000 nest-backend
Enter fullscreen mode Exit fullscreen mode

Head over to http://localhost:3000 to verify your app is running. If it works, you're ready for deployment! ✅


Step 2: Deploying to Render

Now that your NestJS app is Dockerized, let's get it deployed on Render. We have two options:

Option A: The Simple Way (Let Render Build the Image)

This is the easiest way:

  1. Push your code to GitHub/GitLab
  2. In Render Dashboard → New → Web Service
  3. Connect your repository
  4. Render will detect the Dockerfile and build the image automatically

That's it! Render will now build your Docker image from the Dockerfile and deploy your app. Easy. 🎉

Option B: The CI/CD Way (Prebuilt Images with GitHub Actions)

This method gives you more control and enables automatic deployments.

  1. Build the Docker image locally
  2. Push it to Docker Hub
  3. Trigger Render to pull the new image

This also enables automatic CI/CD deployments on every push.

Step 1: Push to Docker Hub

# Build image
docker build -t nest-backend .

# Tag with Docker Hub username
docker tag nest-backend your-username/nest-backend:latest

# Push to Docker Hub
docker push your-username/nest-backend:latest
Enter fullscreen mode Exit fullscreen mode

On Render, choose Deploy an existing Docker image and use:

docker.io/your-username/nest-backend:latest
Enter fullscreen mode Exit fullscreen mode

Render will fetch the latest image whenever you trigger a redeploy.

📝 Notes on tags:

  • Render redeploys based on the tag you specify (latest, v1.0.0, etc.).
  • Using latest is simple, but versioned tags (e.g., v1.0.0) are safer for production since they make rollbacks easier.
  • Render only fetches a new image from Docker Hub when you trigger a redeploy (manually or via CI/CD).

Step 2: Automate with GitHub Actions

Create .github/workflows/docker-build.yml:

name: Build & Push Docker Image
on:
  push:
    branches: [ "main" ]

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout repository
        uses: actions/checkout@v3

      - name: Log in to Docker Hub
        uses: docker/login-action@v2
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}

      - name: Build and push Docker image
        uses: docker/build-push-action@v4
        with:
          context: .
          push: true
          tags: your-username/nest-backend:latest

      - name: Trigger Render Deploy
        run: |
          curl -X POST ${{ secrets.RENDER_DEPLOY_HOOK }}
Enter fullscreen mode Exit fullscreen mode

In your GitHub repo → Settings → Secrets → add:

  • DOCKER_USERNAME → your Docker Hub username
  • DOCKER_PASSWORD → your Docker Hub access token
  • RENDER_DEPLOY_HOOK → from Render dashboard (service → settings → deploy hook URL)

Now every push to main will:

  • Build the Docker image
  • Push it to Docker Hub
  • Trigger a new Render deployment

🎁 Bonus: Keep Your Render App Alive 24/7

On Render’s free tier, your service goes to sleep after 15 minutes of inactivity to save resources. When that happens, the next request can feel slow because the app has to “cold start.”

To prevent this, you can set up a simple heartbeat that pings your app every few minutes.

You don’t have to use Cloudflare Workers — you could also use:

In this tutorial, we’ll go with with Cloudflare Workers (free tier).

Create a Worker

Install Wrangler, Cloudflare’s CLI.

npm install -g wrangler
Enter fullscreen mode Exit fullscreen mode

Generate a new worker:

wrangler init keep-alive-worker
Enter fullscreen mode Exit fullscreen mode

Configure the Worker

In wrangler.toml:

name = "keep-alive-worker"
main = "src/index.ts"
compatibility_date = "2025-09-13"

# Add this
[triggers]
crons = ["*/15 * * * *"]

# (Optional) to enable logs for worker
[observability.logs]
enabled = true
Enter fullscreen mode Exit fullscreen mode

Worker Code

In src/index.ts:

const SERVICES = [
  'https://your-app-name.onrender.com', // Your app's URL
  // Add more services here if needed!
];

export default {
  async scheduled(event, env, ctx) {
    for (const url of SERVICES) {
      try {
        const res = await fetch(url);
        console.log(`✅ Pinged ${url} → ${res.status}`);
      } catch (err) {
        console.error(`❌ Failed to ping ${url}`, err);
      }
    }
  },
};
Enter fullscreen mode Exit fullscreen mode

Deploy the Worker

wrangler deploy
Enter fullscreen mode Exit fullscreen mode

That’s it! 🎉 Your Cloudflare Worker will automatically ping your Render app(s) every 15 minutes, keeping them awake and responsive.

Top comments (0)