Prajwol.
HomeProjectsExperience
Blogs

© 2026 • Made with ❤️ by Prajwol

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

Published At: September 13, 2025

Reading Time: 4 minutes

Author: Prajwol Shrestha

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:

1# Use a lightweight Node.js base image 2FROM node:20-slim 3 4# Set working directory 5WORKDIR /app 6 7# Copy dependency files first (better cache usage) 8COPY package.json pnpm-lock.yaml* ./ 9 10# Install dependencies 11RUN npm install -g pnpm && pnpm install --frozen-lockfile 12 13# Copy the rest of the source code 14COPY . . 15 16# Build the NestJS project 17RUN pnpm run build 18 19# Expose the app’s port 20EXPOSE 3000 21 22# Start the app 23CMD ["pnpm", "run", "start:prod"] 24

Don't Forget the .dockerignore

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

1node_modules 2dist 3.git 4Dockerfile 5.dockerignore 6.env

Test It Locally First

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

1docker build -t nest-backend . 2docker run -p 3000:3000 nest-backend

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
1# Build image 2docker build -t nest-backend . 3 4# Tag with Docker Hub username 5docker tag nest-backend your-username/nest-backend:latest 6 7# Push to Docker Hub 8docker push your-username/nest-backend:latest

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

1docker.io/your-username/nest-backend:latest

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:

1name: Build & Push Docker Image 2on: 3 push: 4 branches: [ "main" ] 5 6jobs: 7 build: 8 runs-on: ubuntu-latest 9 10 steps: 11 - name: Checkout repository 12 uses: actions/checkout@v3 13 14 - name: Log in to Docker Hub 15 uses: docker/login-action@v2 16 with: 17 username: ${{ secrets.DOCKER_USERNAME }} 18 password: ${{ secrets.DOCKER_PASSWORD }} 19 20 - name: Build and push Docker image 21 uses: docker/build-push-action@v4 22 with: 23 context: . 24 push: true 25 tags: your-username/nest-backend:latest 26 27 - name: Trigger Render Deploy 28 run: | 29 curl -X POST ${{ secrets.RENDER_DEPLOY_HOOK }}

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:

  • n8n (self-hosted or cloud)
  • Google Apps Script
  • Any external cron job service

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

Create a Worker

Install Wrangler, Cloudflare’s CLI.

1npm install -g wrangler

Generate a new worker:

1wrangler init keep-alive-worker

Configure the Worker

In wrangler.toml:

1name = "keep-alive-worker" 2main = "src/index.ts" 3compatibility_date = "2025-09-13" 4 5# Add this 6[triggers] 7crons = ["*/15 * * * *"] 8 9# (Optional) to enable logs for worker 10[observability.logs] 11enabled = true

Worker Code

In src/index.ts:

1const SERVICES = [ 2 'https://your-app-name.onrender.com', // Your app's URL 3 // Add more services here if needed! 4]; 5 6export default { 7 async scheduled(event, env, ctx) { 8 for (const url of SERVICES) { 9 try { 10 const res = await fetch(url); 11 console.log(`✅ Pinged ${url} → ${res.status}`); 12 } catch (err) { 13 console.error(`❌ Failed to ping ${url}`, err); 14 } 15 } 16 }, 17};

Deploy the Worker

1wrangler deploy

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

#cicd

#nestjs

#docker

#render