DEV Community

Scott Pritchard
Scott Pritchard

Posted on • Updated on

Self-hosting with Supabase

Please note that this article is a work in progress. Please leave any suggestions or questions in the comments.

Relevant materials:

What is Supabase?

Supabase is an open-source database solution based on Postgres. It includes all the standard features of Postgres with some killer additions such as realtime streams and a REST API. These are all backed up by incredibly powerful libraries.

Pros and cons of self-hosting

There are many reasons you might want to self-host Supabase:

  • More database space - by using your own VPS, you could have a database that is many times bigger than what is (currently) available via Supabase managed platform

  • Full infrastructure control - you're in control of everything so if you need to make a change, you have that option

There are also some reasons you may want to let Supabase host your database instead of self-hosting:

  • It's already setup - You don't have to have a significant level of technical knowledge to setup a Supabase instance on their platform

  • Costs - They offer a free plan which may be enough for your needs

Who is this article for?

This article was written for people who want to self-host Supabase but have perhaps gotten lost with the self-hosting guides out there. Please be aware that at this time, the self-hosted options don't include the dashboard. If that's a deal breaker for you, this guide isn't for you.

To get the most out of this article, it is strongly advised that you have some basic experience with the following:

  • Nginx - Configuring endpoints / proxies
  • Docker - Deploying, configuring Dockerfiles

Even if you're lacking those skills, I've done my best to explain the key parts, as well as the files you need to change.

We will be using Portainer to manage our Docker containers. We'll also be using our service behind Cloudflare. For this, we'll use db.example.com as our domain name. This will be mentioned later in the Nginx configuration section.

Getting setup

The first thing you should do is setup somewhere to host. I personally recommend DigitalOcean as almost everything can be done from the web dashboard. For the purpose of this article, if you see the word 'Droplet', this simply refers to a VPS which will host our Supabase instance.

Supabase provide some one-click deploys for several platforms, but we won't be using those as they don't contain the full Supabase experience.

Instead, we'll be installing Docker. Several VPS providers already have 'ready to deploy' Docker instances. I'd recommend using one as this can save you a significant amount of time compared to setting Docker up manually.

Assuming you have setup a Droplet / VPS with Docker running, the first thing to do is install Portainer. To do this, follow the guide at https://documentation.portainer.io/v2.0/deploy/ceinstalldocker/ . As of writing, there are only 3 steps needed to get Portainer installed:

  1. SSH into your VPS
  2. Run docker volume create portainer_data
  3. Run docker run -d -p 8000:8000 -p 9000:9000 --name=portainer --restart=always -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data portainer/portainer-ce

Next, you need to perform some basic configuration for Portainer. In your web browser, go to http://<ip-of-your-vps>:9000 and follow the on-screen instructions.

Next, we need to setup Nginx. Fortunately, this is very easy with Portainer.

Optional tip before continuing: Setup 2 volumes, nginx-config and nginx-data, and then use those from the volumes tab when setting up Nginx.

From the left-menu of the Portainer dashboard, select 'App Templates' and from the list, select 'Nginx', and then follow the instructions.

Deploying via Portainer

If you read this guide originally, I suggested creating a fork of the supabase repo. This is no longer what I recommend, as a better and more streamlined approach is available.

Going back to the Portainer web dashboard, select 'Stacks' from the left menu, and then click on 'Add Stack'.

Give your stack a name, and select 'Git repository'. For the repository URL, enter the full URL to the supabase repo (https://github.com/supabase/supabase).

In the 'Compose path' field, enter /docker/docker-compose.yml.

Copy the contents of the .env file (https://github.com/supabase/supabase/blob/master/docker/.env.example) to a blank file, and update the following values with valid details:

OPERATOR_TOKEN=your-super-secret-operator-token

JWT_SECRET=your-super-secret-jwt-token-with-at-least-32-characters-long

POSTGRES_PASSWORD=your-super-secret-and-long-postgres-password

# some SMTP server to send your auth-mails with
SMTP_HOST=mail.example.com
SMTP_PORT=
SMTP_USER=
SMTP_PASS=
Enter fullscreen mode Exit fullscreen mode

Under 'Environment variables' click on 'Advanced mode', and then copy and paste the contents of your .env file.

Finally, click 'Deploy the stack' at the bottom. This process might take a few minutes to finish. Once it is completed, click 'Containers' from the Portainer menu. You may notice that supabase-auth isn't running initially. Simply select it, and then click 'Start'. It should then run without any issues.

The final step for this section is configuring the hostname for each Supabase service. From the containers menu item in the Portainers dashboard, select each Supabase container. Click on 'Duplicate/edit' near the top of the page. Then, scroll to the bottom of the page, select the 'Network' tab under 'Advanced container settings'. In the 'Hostname' field, enter the name of the service. For example, for the supabase-rest container, set the hostname to rest. You can set this to anything you like, but I recommend keeping it relevant to the container. Finally, click 'Deploy this container'. It will warn you that a container with the same name already exists - click 'OK'. After a short while, the container will be redeployed with the new hostname. Repeat these steps for each Supabase container.

By doing this, it will help Nginx connect to the container and make it easier to identify in other parts of the infrastructure.

One very important note regarding the supabase-auth container: By default, the DATABASE_URL will include sslmode=disable. The equals sign in this value may cause errors. For example, if you try to call the .signUp() method from the supabase-js library, you may get an error 500. The reason for this is that it will attempt to connect to disable instead of postgres://<db_user>:<password>@db:<db_port>/postgres?sslmode=disable

The solution to this is to ensure that you remove sslmode=disable and any other query parameters from the URL.

Configuring Nginx

In order to setup Nginx to support the various services needed for Supabase, we need to modify our Nginx config.

To do this, you need to SSH into your server, and then cd your way to the volume where your Nginx config is stored. If you set up a nginx-config volume as mentioned above, this will (usually) be located at /var/lib/docker/volumes/nginx-config/_data.

From here, we need to modify 2 files. The first is nginx.conf.

Inside the http section, add the following:

map $http_upgrade $connection_upgrade {
    default upgrade;
    '' close;
}

upstream websocket {
    server realtime:4000;
}
Enter fullscreen mode Exit fullscreen mode

The other file we need to modify is conf.d/default.conf. There's many ways you can configure Nginx, but for the sake of simplicity, we're going to keep this to a single file.

In conf.d/default.conf, replace the entire contents with the following:

server {
    listen 443 ssl;
    server_name db.example.com;

    # REST
    location ~ ^/rest/v1/(.*)$ {
        proxy_set_header Host $host;
        proxy_pass http://kong:8000;
        proxy_redirect off;
    }

    # AUTH
    location ~ ^/auth/v1/(.*)$ {
        proxy_set_header Host $host;
        proxy_pass http://kong:8000;
        proxy_redirect off;
    }

    # REALTIME
    location ~ ^/realtime/v1/(.*)$ {
        proxy_redirect off;
        proxy_pass http://kong:8000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_set_header Host $host;
    }
}
Enter fullscreen mode Exit fullscreen mode

Let me explain what's going on here.

Firstly, our REST handler is listening to all requests sent to /rest/v1 path, as well as anything added after that part of the path. It is then routing it to the Supabase Kong server, which by default will be accessible internally at http://kong:8000.

Likewise, our auth handler is listening for requests to /auth/v1, and routing them also to Kong.

Finally, the realtime subscriptions are handled by the /realtime/v1 route. Again, this routes to Kong.

If you read this guide when it was originally written, I told you to route requests to each individual service. After further investigation, it appears that routing to Kong is the better solution.

The reason we perform this proxying is that it allows us to ensure that requests from the Supabase libraries (e.g. @supabase/supabase-js) are able to correctly route to the different services in the stack.

Kong will handle routing each request to it's appropriate service based upon the URL, while Nginx is providing a way to expose these endpoints under a single URL.

After you've made the changes, save the file, and restart the Nginx container from the Portainer dashboard. To make sure everything is working as expected, select the Nginx container and then 'View logs'. It should indicate that everything is running as expected.

Finally, from the Nginx container (in the Portainer dashboard), connect it to the supabase_default network - the option to do this is found at the bottom of that page in the dashboard.

Necessary Kong configuration

From Portainer, create a new volume from the 'Volumes' menu option. Name the new volume kong-data.

Then, go back to 'containers', and select the 'supabase-kong' container. Click on 'Duplicate/Edit' at the top.

Scroll to the bottom of the page and click 'Volumes'. Click 'Map additional volume'. In the field marked 'Container', enter /var/lib/kong. Then click on the 'Volume' button to the right side of it.

From the dropdown field underneath, select the 'kong-data' volume you created a moment ago. Then click 'Deploy this container'.

GoTrue / auth environment variables

By default, the environment variables for each container should work without any issues, but the GoTrue / auth container may require some additional configuration.

For the sake of simplicity, here is an example environment config you can use in the supabase-auth container - edit it to fit your needs:

GOTRUE_OPERATOR_TOKEN=your-super-secret-operator-token
GOTRUE_JWT_DEFAULT_GROUP_NAME=authenticated

# Make sure this JWT secret matches what was configured during setup
GOTRUE_JWT_SECRET=your-super-secret-jwt-token-with-at-least-32-characters-long

# How long should JWT tokens be valid for?
GOTRUE_JWT_EXP=3600

# Since Supabase is based on Postgres, you shouldn't need to change this
GOTRUE_DB_DRIVER=postgres

# What schema should requests be routed to?
# There should be no reason to change this
DB_NAMESPACE=auth

# Where is our auth/GoTrue located
# You shouldn't need to change these unless the ports are mapped differently
GOTRUE_API_HOST=0.0.0.0
PORT=9999

# Email settings
# You must set these if you want to be able to send emails
GOTRUE_SMTP_HOST=smtp.your-email-host.com
GOTRUE_SMTP_PORT=465
GOTRUE_SMTP_USER=your-smtp-user
GOTRUE_SMTP_PASS=your-smtp-password

# Should users be required to confirm their email address before they can log in?
# If set to false, users won't have to confirm their registration
# If set to true, users will have to click the link in their email to confirm
GOTRUE_MAILER_AUTOCONFIRM=false

# What is the 'from' address that emails are sent from?
GOTRUE_SMTP_ADMIN_EMAIL=noreply@example.com

# Remove this if you don't want debug logs
GOTRUE_LOG_LEVEL=debug

# The connection string for your database
# `@db` says we're looking for the container called 'db' on our docker network
DATABASE_URL=postgres://postgres:your-super-secret-and-long-postgres-password@db:5432/postgres

# Email templates
# Invite user - provide a URL to a HTML or Text template
GOTRUE_MAILER_TEMPLATES_INVITE=https://example.com/path/to/your/invite/template.html

# Confirm registration - provide a URL to a HTML or Text template
GOTRUE_MAILER_TEMPLATES_CONFIRMATION=https://example.com/path/to/your/confirmation/template.html

# Password recovery - provide a URL to a HTML or Text template
GOTRUE_MAILER_TEMPLATES_RECOVERY=https://example.com/path/to/your/password_reset/template.HTML

# Magic link - provide a URL to a HTML or Text template
GOTRUE_MAILER_TEMPLATES_MAGIC_LINK=https://example.com/path/to/your/magic_link/template.html

# GoTrue URLs
# These are appended after the API_EXTERNAL_URL
# You shouldn't need to change these
GOTRUE_MAILER_URLPATHS_CONFIRMATION=/auth/v1/verify
GOTRUE_MAILER_URLPATHS_INVITE=/auth/v1/verify
GOTRUE_MAILER_URLPATHS_CONFIRMATION=/auth/v1/verify
GOTRUE_MAILER_URLPATHS_RECOVERY=/auth/v1/verify

# Site URLs
# This is where the user will be redirected to after clicking a link in an email and after oAuth
GOTRUE_SITE_URL=https://example.com/redirect_to_here
GOTRUE_URI_ALLOW_LIST=https://example.com/redirect_to_here

# This is the URL where your supabase stack is accessible
# i.e. this is the endpoint URL you would pass into a `createClient()` call in the supabase-js library
API_EXTERNAL_URL=https://database.example.com/

# Set this to true if you want to prevent signing up with email and password
GOTRUE_DISABLE_SIGNUP=true

# oAuth
# If you are not using oAuth to login (e.g. Login with Facebook), you can ignore the below
# If you want to disable oAuth for a specific provider, set the `GOTRUE_EXTERNAL_<provider>_ENABLED` to false
# Github oAuth
GOTRUE_EXTERNAL_GITHUB_CLIENT_ID=your_github_client_id
GOTRUE_EXTERNAL_GITHUB_SECRET=your_github_client_secret
GOTRUE_EXTERNAL_GITHUB_ENABLED=true

# Google oAuth
GOTRUE_EXTERNAL_GOOGLE_CLIENT_ID=your-google-client-id.apps.googleusercontent.com
GOTRUE_EXTERNAL_GOOGLE_SECRET=your-google-secret
GOTRUE_EXTERNAL_GOOGLE_ENABLED=true

# Facebook oAuth
GOTRUE_EXTERNAL_FACEBOOK_CLIENT_ID=your-facebook-client-id
GOTRUE_EXTERNAL_FACEBOOK_SECRET=your-facebook-app-secret
GOTRUE_EXTERNAL_FACEBOOK_ENABLED=true

# Add other oAuth provider details below
Enter fullscreen mode Exit fullscreen mode

Final checks

At this point, everything should be running as expected.

You should be able to connect to your Postgres server using your VPS IP address, port 5432, and using the username postgres and the postgres password you setup inside the environmental variables.

Closing notes

As stated previously, this article is a work in progress. I am still working through some of the finer details of configuring the Supabase stack for self-hosting. There might be some things missing from the article - leave a comment to let me know, and I'll look into adding it.

Setting up Supabase for self-hosting is by no means a quick task, and there's still room for improvement. Hopefully, this article will help speed up the process for you and set you off on the journey.

Top comments (16)

Collapse
 
thewebisyourfriend profile image
Tony Westaway

What digital Ocean droplet would be suitable for this? Let's say I wanted to run 6 databases for example. I'm new to full stack and finding it all very expensive. I have a few ideas for web apps but the cost of running them seems high just for my own personal use, and potentially a few other users.

Collapse
 
chronsyn profile image
Scott Pritchard

You could realistically run the Supabase stack on a 2GB / 1 VCPU droplet, which costs $10 a month.

In terms of hosting multiple databases, if it was just postgres, you'd be able to create multiple databased on a server, but to get the Supabase stack to work, you'd potentially need to fork their code and make some adjustments and the manage creating the docker images yourself.

You could also create 1 droplet for each project, and this would be what I'd suggest, but the costs can be quite prohibitive. One possible solution is to host some projects on a free Supabase plan (i.e. on their managed platform), and some self-hosted.

Another option would be to think whether you need 6 independent projects. If money is an issue, thinking smart about how your tables are structured can significantly reduce your financial overhead. For example, if you have an app and an API, and both need database storage, you could prefix some tables with app_ and others with api_.

It's not something I'd recommend for most situations, but it's certainly an option.

Remember that you can run additional containers on your Supabase droplet. If you have an API that needs to access the database, instead of hosting it on a separate droplet, create a docker image of it and host it on the same droplet as Supabase. That saves you the monthly cost of 1 droplet.

Collapse
 
treboryx profile image
Robert • Edited

Nice guide. How about oauth providers? Can you set them up with the self host version?

EDIT: I think I might've got it, it's right here:
github.com/supabase/gotrue#externa...

Collapse
 
chronsyn profile image
Scott Pritchard • Edited

You can - I'm going to be updating the guide soon with the details needed to configure oAuth on self-hosted.

EDIT - 17:52, 01 August 2021:
The environment config example for oAuth support has been added near the bottom of the article

Collapse
 
j2l profile image
Philippe Manzano

Thank you very much!
Just to let you know for OS wise additions: On Ubuntu 21.04 (home setup for server), you have to sudo for everything about docker. Portainer is more picky, you can't simply use stack to github docker-compose.yml as is,
I had to add version: "3" , remove # github.com/supabase/cli/issues/14
and change all ports in .env since I had to use docker-compose up -d and it creates a docker-default network that seems to see bridge (and I'm running other postgres and nodejs containers). I guess it should have its own network set in docker-compose.
Additionally, on portainer running on Ubuntu, you usually can't edit a container without stopping it first (sometimes you can though).
I preferred to go local and use binds to my clone of supabase/docker/volumes/ instead of docker far far away volumes /var/lib/docker/volumes/ with sudo everywhere.
I'm missing a ssl certificate on this local docker, so nginx default.conf has listen 80
Reading supabase.io/docs/guides/self-hosting, it looks like, if you change the JWT_SECRET, you need to generate JWTs to add to your docker-coimpose.yml. I guess with jwt.io/?
Now, I have all the containers running but I don't know how to add a new user + project or base.
I know how to use supabase UI, and I locally can open db using Adminer but everything seems pretty empty, no user table.
Would you please let us know about using supabase from docker?
Thank you again,

Collapse
 
christopherkapic profile image
Christopher Kapic

Truly a fantastic resource Scott! Would you mind if I made a video walkthrough of this article? I'll link back to it in the description.

Collapse
 
chronsyn profile image
Scott Pritchard

Absolutely! Be sure to post a link here and I'll add it to the article!

Collapse
 
christopherkapic profile image
Christopher Kapic

One question--Portainer and Kong both use port 8000. How did you handle this?

Thread Thread
 
chronsyn profile image
Scott Pritchard

The Supabase stack is on it's own network and I believe this is what prevents the routing from getting confused. Portainer uses ports 8000 and 9000 - 9000 is for the GUI, and 8000 is an SSH tunnel server used to create a tunnel between portainer agents and the portainer instance.

My Nginx config points towards the Kong instance (e.g. proxy_pass http://supabase-kong:8000;), which in turn handles routing to each individual service. The portainer container, being on a separate network, doesn't even know that the Supabase stack exists at the network level.

Thread Thread
 
Sloan, the sloth mascot
Comment deleted
Collapse
 
f041 profile image
Gabriele Giordano

about the passwords in the env part, this might look useful: supabase.com/docs/guides/self-host...
but then it produces a

Failed to deploy a stack: kong Pulling rest Pulling functions Pulling realtime Pulling storage Pulling vector

Collapse
 
mirsahib profile image
Mir Habib Ul Latif

What is the minimum system requirements for supabase stack to run in vps

Collapse
 
abdalaliy11 profile image
abdalaliy1

how to do everything without dashboard easily?
maybe it's worth another article

Collapse
 
chronsyn profile image
Scott Pritchard

Most of what I've covered within the article should be sufficient to get someone from nothing to self-hosted within a few hours.

Just about everything that's not covered is SQL, and I have no plans for an article covering that at this time as there are a huge number of resources out there which cover thousands of possible use cases in detail.

Collapse
 
willdvlpr profile image
Will Jones

Scott, brilliant article! Found to be very helpful!

Collapse
 
tomitrescak profile image
Tomas Trescak

Hi, thanks for the amazing resource. My nginx complains about folowing:

2021/09/16 02:42:00 [emerg] 1#1: host not found in upstream "realtime:4000" in /etc/nginx/nginx.conf:39
Enter fullscreen mode Exit fullscreen mode

Any idea?