Multi-Service Architectures on Render

This guide teaches you how to:

  • Combine different Render service types into a common web app architecture
  • Set up connections between services using environment variables
  • Communicate between services over a private network

Modern cloud applications usually consist of multiple connected services:

Web App
Frontend
Backend
DB
Clients
(Browsers, etc.)

A multi-service architecture like this one enables you to deploy, scale, and even swap out individual parts of your app—all with minimal impact on the rest of your system.

The Render platform is designed from the ground up to support multi-service architectures. You can assemble different service types into any combination you need, using any set of languages and frameworks.

Let’s look at an example.

Example scenario

A common multi-service web app might consist of:

  • A React or Next.js website for the app’s frontend
  • An Express or Django API server to handle requests from clients
  • A relational database for long-term storage of application data

We can represent each of these components as a separate service on Render:

ComponentService TypeCommon Frameworks
FrontendStatic site (or a web service if the frontend includes server-side logic)React, Next.js, Vue.js
BackendWeb serviceDjango, Express, FastAPI
DatabasePostgreSQL database

Let’s walk through deploying an app with these components. You can apply these steps to your own app, regardless of which frameworks you use.

Prerequisites

Before we start deploying, confirm all of the following:

  1. You’ve created your Render account.
  2. Each project you want to deploy is one of the following:
    • A repository hosted on GitHub, GitLab, or Bitbucket
    • A Docker image in a supported registry, such as Docker Hub
  3. Your full application works as expected on your local machine.
  4. You’ve consulted your chosen framework’s documentation for specific deployment guidance.

Steps to deploy

In the steps below, we’ll first deploy each component of our architecture. Then, we’ll connect them by setting environment variables.

1. Create a database

Render provides fully managed PostgreSQL databases with automatic daily backups, along with advanced recovery features for larger instances.

We can create a PostgreSQL database with a few clicks in the Render Dashboard:

Creating a new PostgreSQL database in the Render Dashboard

Follow these steps, then return here.

2. Deploy the backend

Our application’s backend will handle incoming HTTP requests from browsers and other clients. To support this, we’ll create a web service.

Web services receive a public onrender.com URL, and you can add your own custom domains. You can use virtually any web framework for your web service (Django, Express, FastAPI, and so on).

To deploy a backend that only receives traffic from your own Render infrastructure, create a private service instead.

  1. Make sure that on startup, your backend code binds an HTTP server to a port on host 0.0.0.0.

    • We recommend binding to the value of the PORT environment variable (default 10000).

    • If you’re building from a Dockerfile, indicate your HTTP port in the file like so:

      EXPOSE 10000

      Learn more about the EXPOSE instruction.

  2. Create a new web service in the same region as your database and deploy your backend code to it.

3. Deploy the frontend

Our application’s frontend will serve the content that users view and interact with in their browser.

Depending on our frontend’s framework, we’ll deploy our code as either a static site or a second web service:

Service typeWhen to useExample frameworks
Static siteFor apps with entirely static content (HTML/CSS/JS)

React, Vue.js, Next.js (static exports only)

Web serviceFor apps with server-side logicNext.js, Nuxt.js

Render static sites are served by a globally distributed CDN, so we recommend using them for any framework that supports it.

To deploy your frontend, follow the instructions for your chosen service type, then return here:

4. Connect your services

After creating and deploying our services, we need to configure them to communicate with each other. To do this, we can set environment variables on a service to specify the address of each other service it connects to:

Render
BACKEND_URL
DATABASE_URL
FRONTEND_URL
Static Site
Web Service
DB

Let’s set up connections for our frontend and backend services.

Update the frontend service

  1. Look up the public URL of your backend service.

  2. Add an environment variable to your frontend service:

    • Give the environment variable a helpful name (such as BACKEND_URL) and set its value to your backend’s public URL.
    • For static site frameworks, you might need to use a specific name for the environment variable (such as REACT_APP_BACKEND_URL for React).
  3. Update your frontend code to use the new environment variable to connect to your backend. For example, in JavaScript:

    // Use BACKEND_URL if set, otherwise default to localhost
    const BACKEND_URL = process.env.BACKEND_URL || "http://localhost:4000";
    
    // Basic example of fetching data from your backend
    fetch(`${BACKEND_URL}/api/data`)
        .then(response => response.json())
        .then(data => console.log(data));
  4. Push your updated code to your linked branch to deploy your changes.

Update the backend service

  1. Look up the public URL of your frontend service.

  2. Look up the internal address of your PostgreSQL database.

    • Backend services on Render can communicate with each other using their “internal” (or “private”) addresses. When you use an internal address, traffic between the services stays on their private network—it doesn’t traverse the open internet.
  3. Add environment variables to your backend service:

    • Define a FRONTEND_URL variable and set its value to the frontend’s public URL.
    • Define a DATABASE_URL variable and set its value to the database’s internal address.
  4. Update your backend code to use the FRONTEND_URL and DATABASE_URL environment variables to connect. See examples below.

    • Using FRONTEND_URL to set CORS headers in Express middleware:

      // Use FRONTEND_URL if set, otherwise default to localhost
      const FRONTEND_URL = process.env.FRONTEND_URL || "http://localhost:3000";
      
      // Set CORS headers to allow requests from the frontend
      app.use((req, res, next) => {
          res.setHeader("Access-Control-Allow-Origin", FRONTEND_URL);
          next();
      });
    • Using DATABASE_URL to connect to a database with the pg Node.js library:

      const { Pool } = require("pg");
      
      const pool = new Pool({
          connectionString: process.env.DATABASE_URL
      });
  5. Push your updated code to your linked branch to deploy your changes.

After your frontend and backend deploys complete, your app should be up and running! Visit your frontend URL to confirm. If you encounter any issues, see Troubleshooting Deploys.

Consider infrastructure as code (IaC)

As your architecture grows in scale, it becomes more and more helpful to manage your services in a unified way. Render Blueprints enable you to configure and update the entire architecture of your app with a single YAML file.

+ Show example Blueprint
# This is a basic example Blueprint for a Django web service and
# the PostgreSQL database it connects to.
services:
  - type: web # A Python web service named django-app running on a free instance
    plan: free
    name: django-app
    runtime: python
    repo: https://github.com/render-examples/django.git
    buildCommand: "./build.sh"
    startCommand: "python -m gunicorn mysite.asgi:application -k uvicorn.workers.UvicornWorker"
    envVars:
      - key: DATABASE_URL # Sets DATABASE_URL to the connection string of the django-app-db database
        fromDatabase:
          name: django-app-db
          property: connectionString

databases:
  - name: django-app-db # A PostgreSQL database named django-app-db running on a free instance
    plan: free

You can even generate a Blueprint for your existing services, which makes it much faster to get started.