Skip to content

How to Deploy NocoDB on a VPS

Updated Sep 2026

verified on Ubuntu 26.04 · Sep 2026
We earn commissions when you shop through the links below. Full disclosure →

Self-host NocoDB, the spreadsheet-style Airtable alternative, on your own VPS with Docker Compose, PostgreSQL, Redis and HTTPS, plus the backups and upgrades that keep it safe.

Before you start
  • A VPS with at least 2 GB RAM (NocoDB's own container idles around 1.5 GB in our test)
  • A fresh Ubuntu 24.04 or 26.04 server with root/sudo SSH access
  • A domain you can point at the server
  • Docker Engine + Compose installed (see the base guide below)
Need a box for this guide? Kamatera's free tier lets you spin one up now.Start free on Kamatera → (opens in new tab)

What NocoDB is

NocoDB puts a spreadsheet-style interface on top of a relational database. You get grid, kanban, calendar, gallery and form views, a REST API for every table, and sharing and permissions. You get all of it without writing SQL. It can keep its tables in its own PostgreSQL database, or you can connect an existing PostgreSQL or MySQL database and work on those tables in place.

It is the usual self-hosted answer to Airtable. The other one is Baserow, and NocoDB vs Baserow covers the differences. The short version: NocoDB is the better fit when you want a UI over a database you already run, and Baserow is the more self-contained product. Check the licence before you commit. The catalog lists NocoDB under the Sustainable Use License, not an OSI licence, so read it if you plan to resell or embed NocoDB.

Server sizing

On our test box (GCP e2-standard-2, Ubuntu 26.04, Docker), a NocoDB stack idled at about 1,557 MB of RAM and used about 2.2 GB of disk for images and data. That was the stack with NocoDB, a worker, PostgreSQL and Redis. The catalog's minimum is 2 GB RAM. Treat that as the floor:

  • 2 GB RAM / 1–2 vCPU: fine for one person or a small team with modest tables.
  • 4 GB RAM / 2 vCPU: the comfortable choice. You get headroom for imports, the worker's background jobs, and the reverse proxy.
  • Disk: start with 20–40 GB. Attachments are stored in the NocoDB data volume unless you configure S3-compatible storage, so size the disk for your uploads.

Prepare the server

This guide assumes Docker Engine and the Compose plugin are installed, along with a firewall. If not, work through Docker & Compose on Ubuntu first.

Only SSH and the web ports need to be open. NocoDB itself will listen on loopback only:

sudo ufw allow OpenSSH
sudo ufw allow 80
sudo ufw allow 443
sudo ufw --force enable
sudo ufw status verbose
Where to host itaffiliate disclosure
Hetzner Cloudrun it on
2 vCPU · 4 GB RAM · 80 GB SSD · $23.59/mo
Get Hetzner Cloud (opens in new tab)
Kamaterafree trial
1 vCPU · 1 GB RAM · 20 GB SSD · $4.00/mo
Start free on Kamatera → (opens in new tab)
DigitalOceanalso works on
1 vCPU · 1 GB RAM · 25 GB SSD · $6.00/mo
Deploy on DigitalOcean → (opens in new tab)

Paid link — we earn a commission if you shop through it.

Install NocoDB (Docker Compose)

NocoDB publishes a plain Compose stack (the quickstart-demo example in its repository). It runs the NocoDB app, a background worker for imports, exports and automations, PostgreSQL and Redis. The version below follows that file, with three changes for a server:

  • It uses generated secrets instead of the demo password.
  • It binds the app to 127.0.0.1 so that only the reverse proxy can reach it.
  • It sets NC_SITE_URL to your public URL.

Create the project directory and an .env file with generated values:

mkdir -p ~/nocodb && cd ~/nocodb
if [ ! -f .env ]; then
  cat > .env <<EOF
POSTGRES_PASSWORD=$(openssl rand -hex 24)
NC_AUTH_JWT_SECRET=$(openssl rand -hex 32)
NC_SITE_URL=https://nocodb.example.com
EOF
fi
chmod 600 .env

The if guard stops you overwriting the database password on a second run. Change the password after PostgreSQL has initialised and NocoDB can no longer log in. Replace nocodb.example.com with your real hostname. NocoDB builds invitation and password-reset links from NC_SITE_URL, so a wrong value produces broken links.

Now the Compose file:

cd ~/nocodb
cat > docker-compose.yml <<'YAML'
services:
  nocodb:
    image: nocodb/nocodb:latest
    restart: unless-stopped
    environment:
      NC_DB: "pg://db:5432?u=nocodb&p=${POSTGRES_PASSWORD}&d=nocodb"
      NC_REDIS_URL: "redis://redis:6379"
      NC_SITE_URL: ${NC_SITE_URL}
      NC_AUTH_JWT_SECRET: ${NC_AUTH_JWT_SECRET}
      NC_DISABLE_MUX: "true"
    depends_on:
      db: { condition: service_healthy }
      redis: { condition: service_healthy }
    volumes:
      - nocodb_data:/usr/app/data
    ports:
      # Loopback only: Caddy is the public entry point.
      - "127.0.0.1:8080:8080"
    healthcheck:
      test: ["CMD-SHELL", "wget -q --tries=1 --spider http://localhost:8080/api/v1/health || exit 1"]
      interval: 30s
      timeout: 5s
      retries: 5
      start_period: 30s

  worker:
    image: nocodb/nocodb:latest
    restart: unless-stopped
    environment:
      NC_DB: "pg://db:5432?u=nocodb&p=${POSTGRES_PASSWORD}&d=nocodb"
      NC_REDIS_URL: "redis://redis:6379"
      NC_SITE_URL: ${NC_SITE_URL}
      NC_AUTH_JWT_SECRET: ${NC_AUTH_JWT_SECRET}
      NC_WORKER_CONTAINER: "true"
    depends_on:
      nocodb: { condition: service_healthy }
    volumes:
      - nocodb_data:/usr/app/data

  db:
    image: postgres:17.10
    restart: unless-stopped
    environment:
      POSTGRES_USER: nocodb
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: nocodb
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U nocodb -d nocodb"]
      interval: 10s
      timeout: 5s
      retries: 5

  redis:
    image: redis:7
    restart: unless-stopped
    volumes:
      - redis_data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5

volumes:
  nocodb_data:
  postgres_data:
  redis_data:
YAML

Bring it up. --wait blocks until the health checks pass, which takes a minute or two on the first run while the images download:

cd ~/nocodb
docker compose up -d --wait
docker compose ps

Check that the app answers on loopback. Expect a 200:

curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8080/api/v1/health

If you'd rather not maintain a Compose file, NocoDB also ships a single-server installer, noco.sh, that generates this stack plus Traefik and Let's Encrypt for you. It is the method shown on the NocoDB page. The hand-written file above is easier to read, back up and diff.

HTTPS + domain

Point an A record for nocodb.example.com at the server's public IP. Then put Caddy in front of 127.0.0.1:8080. The full setup is in Automatic HTTPS with Caddy. The site block is:

nocodb.example.com {
    reverse_proxy 127.0.0.1:8080
}

Caddy passes WebSocket upgrades through without extra configuration. Reload Caddy, then open https://nocodb.example.com.

First login and securing it

The first account to sign up becomes the super admin. Do it straight after the site goes live, before anyone else finds the URL. Then:

  • Close public signup. In the super-admin settings, switch signups to invite-only. The environment-variable form is NC_INVITE_ONLY_SIGNUP. After that, new users only get in through an invitation.
  • Keep the app port private. The Compose file binds 8080 to loopback. Do not change it to 8080:8080. Docker-published ports bypass ufw, so that change would expose NocoDB over plain HTTP.
  • Protect .env. It holds the database password and the JWT secret. It is chmod 600 and should never go into git.
  • Set up email if you want invitations and password resets to work. The SMTP variables are listed in NocoDB's environment-variable reference.
  • Telemetry: NocoDB sends anonymous usage data by default. Add NC_DISABLE_TELE: "true" to both app services to turn it off.

Backups

Your tables and NocoDB's metadata live in PostgreSQL. Uploaded attachments live in the nocodb_data volume. Back up both.

A logical dump of the database is safe to take while the stack is running:

cd ~/nocodb
mkdir -p backups
docker compose exec -T db pg_dump -U nocodb -d nocodb | gzip > backups/nocodb-db-$(date +%F).sql.gz
ls -lh backups/

Attachments come from the data volume. Compose prefixes the volume name with the project directory, so check the real name with docker volume ls:

cd ~/nocodb
docker run --rm -v nocodb_nocodb_data:/data:ro -v "$PWD/backups":/backup alpine \
  tar czf /backup/nocodb-data-$(date +%F).tar.gz -C /data .

Copy backups/ and your .env off the server. Without the .env file you have the data but not the secrets that match it. To restore, start a fresh stack with the same .env. Load the dump with gunzip -c … | docker compose exec -T db psql -U nocodb -d nocodb, then unpack the attachments archive into the new volume.

Upgrades

cd ~/nocodb
docker compose pull
docker compose up -d --wait

Take the pg_dump backup above first, every time, because a new version can change the database schema when it starts. latest follows current releases. If you'd rather choose when to upgrade, pin nocodb/nocodb to a version tag from its releases page and change the tag deliberately. Read the release notes before a big version jump.

Troubleshooting

docker compose up --wait times out on nocodb. Read the logs with docker compose logs nocodb --tail 100. The usual cause is a database connection error, because the password in NC_DB no longer matches the one PostgreSQL was first initialised with. PostgreSQL only reads POSTGRES_PASSWORD when its volume is empty, so changing .env later does not change the password.

Invitation or reset links point to localhost. NC_SITE_URL is unset or wrong. Fix it in .env and run docker compose up -d.

Imports hang. Imports and exports run on the worker container. Check that it is running (docker compose ps) and read docker compose logs worker.

The box runs out of memory. The stack idles around 1.5 GB. Large CSV imports push it higher. Move to 4 GB, or add swap as a stopgap.

Verification + next steps

You're done when:

  • https://nocodb.example.com loads with a valid certificate;
  • you have signed in as the super admin and closed public signup;
  • a test base survives docker compose restart;
  • a dated pg_dump sits somewhere other than this server.

From there, connect an existing database as a data source, or try the REST API with a scoped API token. If you're still choosing, compare it with Baserow on a VPS or look at Directus for a headless data platform. For ranked hosts, see Best VPS for databases.

Next steps

How to self-host NocoDB →More self-hosted no-code database tools →Automatic HTTPS with Caddy →Run Claude Code with Ollama on Your Own VPS →Deploy Coolify on a VPS →How to Deploy Actual Budget on a VPS →How to Deploy AnythingLLM on a VPS →How to Deploy Appwrite on a VPS →How to Deploy Audiobookshelf on a VPS →How to Deploy Authelia on a VPS →How to Deploy authentik on a VPS →How to Deploy Baserow on a VPS →How to Deploy Beszel on a VPS →How to Deploy Bitwarden on a VPS →How to Deploy BookStack on a VPS →How to Deploy CapRover on a VPS →How to Deploy Checkmate on a VPS →How to Deploy Directus on a VPS →How to Deploy docker-mailserver on a VPS →How to Deploy Docmost on a VPS →How to Deploy Dokku on a VPS →How to Deploy Dokploy on a VPS →How to Deploy Firefly III on a VPS →How to Deploy Forgejo on a VPS →How to Deploy Gatus on a VPS →How to Deploy Ghostfolio on a VPS →How to Deploy Gitea on a VPS →How to Deploy GitLab on a VPS →How to Deploy GlitchTip on a VPS →How to Deploy Grafana on a VPS →How to Deploy Graylog on a VPS →How to Deploy Headscale on a VPS →How to Deploy Healthchecks on a VPS →How to Deploy Home Assistant on a VPS →How to Deploy Immich on a VPS →How to Deploy Jan on a VPS →How to Deploy Jellyfin on a VPS →How to Deploy Karakeep on a VPS →How to Deploy Keycloak on a VPS →How to Deploy Leantime on a VPS →How to Deploy LibreChat on a VPS →How to Deploy Linkwarden on a VPS →How to Deploy LocalAI on a VPS →How to Deploy Mailcow on a VPS →How to Deploy Mailu on a VPS →How to Deploy Matomo on a VPS →How to Deploy Mattermost on a VPS →How to Deploy Meilisearch on a VPS →How to Deploy Memos on a VPS →How to Deploy n8n on a VPS →How to Deploy Navidrome on a VPS →How to Deploy NetBird on a VPS →How to Deploy Netdata on a VPS →How to Deploy Nextcloud on a VPS →How to Deploy Next.js to a VPS →How to Deploy Nginx Proxy Manager on a VPS →How to Deploy ntfy on a VPS →How to Deploy Ollama on a VPS →How to Deploy Open WebUI on a VPS →How to Deploy OpenHands on a VPS →How to Deploy OpenObserve on a VPS →How to Deploy OpenProject on a VPS →How to Deploy Outline on a VPS →How to Deploy Pangolin on a VPS →How to Deploy Paperless-ngx on a VPS →How to Deploy Passbolt on a VPS →How to Deploy Plane on a VPS →How to Deploy Plausible Analytics on a VPS →How to Deploy Pocket ID on a VPS →How to Deploy PocketBase on a VPS →How to Deploy Prometheus on a VPS →How to Deploy Psono on a VPS →How to Deploy Radarr on a VPS →How to Deploy Rocket.Chat on a VPS →How to Deploy SigNoz on a VPS →How to Deploy Sonarr on a VPS →How to Deploy Stalwart on a VPS →How to Deploy Stirling-PDF on a VPS →How to Deploy Supabase on a VPS →How to Deploy Synapse on a VPS →How to Deploy Taiga on a VPS →How to Deploy TeamPass on a VPS →How to Deploy Tinyauth on a VPS →How to Deploy Traefik on a VPS →How to Deploy Trilium on a VPS →How to Deploy Twenty CRM on a VPS →How to Deploy Umami on a VPS →How to Deploy Uptime Kuma on a VPS →How to Deploy Vaultwarden on a VPS →How to Deploy Vikunja on a VPS →How to Deploy wg-easy on a VPS →How to Deploy Wiki.js on a VPS →How to Deploy Zabbix on a VPS →How to Deploy Zitadel on a VPS →How to Deploy Zulip on a VPS →Docker & Compose on Ubuntu 26.04 →Building AI Workflows with n8n →Install Open WebUI with Ollama →Adding AI-Powered Insights to Plausible Analytics →Building AI-Powered Apps with Supabase and pgvector →

We use analytics cookies (Google Analytics, PostHog) to see which guides are useful. No ad networks, no cross-site tracking. See our privacy policy.