What you will set up
NocoDB turns a database into something that works like a spreadsheet. Your team keeps customers, projects, orders or stock in tables, links records between them, and looks at the same data as a grid, a kanban board, a gallery or a calendar. A form view collects records from people without an account, a shared link shows a view read-only, and everything can be read and written over a REST API and webhooks.
NocoDB is made by NocoDB, Inc., which also sells NocoDB Cloud and paid licences for running it yourself. Since version 0.301.0, released in January 2026, NocoDB is no longer open source: it moved from the AGPL to the Sustainable Use License, the "fair-code" licence n8n also uses. It lets you use and change NocoDB for free for your own company's internal work, and for personal or non-commercial use. It does not let you sell it, or run it as a service for others for money, for example as a hosted database for your customers. You may pass it on only for free and for non-commercial purposes. Versions up to 0.265 remain under the AGPL.
What you get for free is the Community Edition. The interface shows the rest with a Business, Enterprise, Add-on or Scale badge, and in our tests these needed a paid licence: two-factor authentication, single sign-on with OIDC or SAML, teams, SCIM, the admin audit log, table and field permissions, row-level permissions, timeline and Gantt views, unique fields, colours for rows and cells, custom links and logos for shared forms, dashboards, sync with other services, and the AI features. The free edition has one workspace. Under Account settings > Profile, Show only community edition features hides the badges.
Here NocoDB runs as a pod under a user of its own called nocodb, from NocoDB's official image, with PostgreSQL for its data, behind Caddy from the Podman guide.
Every step below was run on a fresh Melonslab VC-P Alloy (2 vCPU, 8 GB) with Debian 13:
- NocoDB 2026.09.0 started with PostgreSQL 17. The super admin was created over an SSH tunnel, and sign-up was closed, before Caddy was set up. From outside, a sign-up got "Not allowed to signup, contact super admin."
- Three users were invited, two with an emailed link and one with a copied link. The ones with a role saw the base; the one without a role saw nothing.
- A base with two tables was created, with text, email, phone, single select, date, currency, checkbox and attachment fields, and a link from projects to customers. A kanban view grouped the projects by status.
- A shared grid view opened without a login and could not be edited. A public form added a record. A file uploaded to a record could only be downloaded through a link that expires.
- An API token listed and created records over the REST API, and a webhook posted each new record to another service, also to another app on the same server with the setting from the Podman guide.
- The interface was shown in Swedish.
- A capture of the server's traffic showed one outside connection, to GitHub, to check for updates. A script that the browser loads from Chatwoot, a support chat service, was blocked by Caddy.
- An invite and a password reset were sent through an SMTP test server on the same machine. Without email, the reset silently sent nothing, as step 6 describes.
- NocoDB was restored from a backup into empty volumes, updated to 2026.09.1, and everything, logins included, came back by itself after a reboot.
The pod used about 850 MB of memory, about 800 MB of it NocoDB.
Before you start
You need:
- a server set up as in the Podman guide, with Caddy running;
- an A record and an AAAA record for
nocodb.example.compointing at your server.
The examples use nocodb.example.com for NocoDB and 203.0.113.10 for your server's IPv4 address. Replace them throughout.
1. Create the user
As root:
useradd -m -s /bin/bash nocodb
loginctl enable-linger nocodb
machinectl shell nocodb@
Everything up to step 5 runs as nocodb.
2. Create the secrets
password=$(openssl rand -hex 24)
printf %s "$password" | podman secret create nocodb-db-password -
printf %s "pg://127.0.0.1:5432?u=nocodb&p=$password&d=nocodb" | podman secret create nocodb-db-url -
unset password
openssl rand -hex 32 | tr -d '\n' | podman secret create nocodb-jwt-secret -
The first three lines make a database password and store it twice: on its own for PostgreSQL, and inside the connection address, NC_DB, that NocoDB uses. The last line makes the key NocoDB signs its logins with. Without a key of your own, NocoDB makes a new one when it starts, and everyone is logged out. Podman stores all three, not the files below.
3. Define the pod
mkdir -p ~/.config/containers/systemd
cd ~/.config/containers/systemd
Create nocodb.pod:
[Pod]
PodName=nocodb
# Only Caddy, on this server, can reach NocoDB: the port is not open to the internet.
PublishPort=127.0.0.1:8116:8080
[Install]
WantedBy=default.target
Create nocodb-db.container:
[Container]
ContainerName=nocodb-db
Image=docker.io/library/postgres:17
Pod=nocodb.pod
Volume=nocodb-db:/var/lib/postgresql/data
Environment=POSTGRES_USER=nocodb POSTGRES_DB=nocodb
Secret=nocodb-db-password,type=env,target=POSTGRES_PASSWORD
HealthCmd=pg_isready -U nocodb -d nocodb
HealthInterval=10s
Notify=healthy
AutoUpdate=registry
[Service]
Restart=always
And nocodb-app.container:
[Unit]
Requires=nocodb-db.service
After=nocodb-db.service
[Container]
ContainerName=nocodb-app
Image=docker.io/nocodb/nocodb:2026.09.1
Pod=nocodb.pod
Volume=nocodb-data:/usr/app/data
Environment=NC_SITE_URL=https://nocodb.example.com
Environment=NC_DISABLE_TELE=true NC_DISABLE_ERR_REPORTS=true NC_DISABLE_SUPPORT_CHAT=true
Environment=NC_SECURE_ATTACHMENTS=true
Secret=nocodb-db-url,type=env,target=NC_DB
Secret=nocodb-jwt-secret,type=env,target=NC_AUTH_JWT_SECRET
[Service]
Restart=always
What the settings do:
NC_DBpoints NocoDB at PostgreSQL in the same pod. NocoDB keeps both its own settings and your tables there; without it, it uses a SQLite file.NC_SITE_URLis the public address NocoDB puts in invite and password reset links.NC_DISABLE_TELE,NC_DISABLE_ERR_REPORTSandNC_DISABLE_SUPPORT_CHATswitch off usage statistics, error reports and the support chat. Step 14 has what is left.NC_SECURE_ATTACHMENTS=truemakes uploaded files downloadable only through links that expire after about two hours, instead of a fixed address that works without a login. NocoDB's documentation now calls thisNC_ATTACHMENT_ACCESS_CONTROL_ENABLED, but in 2026.09 only the older name worked.- The uploaded files go in the
nocodb-datavolume, the rest in the database.
NocoDB stays on an exact version, because it changes its database when it updates, and an update should start with a backup (see Update). PostgreSQL follows version 17 by itself through podman-auto-update.timer. Start it:
systemctl --user daemon-reload
systemctl --user start nocodb-pod
systemctl --user enable --now podman-auto-update.timer
The first start downloads about 1.4 GB of images, and NocoDB takes a minute to set up its database. It is ready when this prints {"message":"OK" and some more:
curl -s http://127.0.0.1:8116/api/v1/health
4. Create the super admin over an SSH tunnel
Until someone signs up, the first person to open NocoDB becomes its super admin, and after that anyone can sign up. Caddy does not show NocoDB to anyone yet, so do both through an SSH tunnel, which only you have. Our test server got its first visit from a scanner three seconds after its certificate was issued.
On your own computer, open a tunnel to NocoDB's port on the server, and keep it open:
ssh -L 8116:127.0.0.1:8116 root@203.0.113.10
Open http://localhost:8116 in your browser. It shows SIGN UP. Enter your E-mail and a long Password, leave Subscribe to our weekly newsletter off, and choose SIGN UP. NocoDB asks what you plan to use it for; Skip at the bottom passes on that.
Now close sign-up. Open your account menu at the bottom left, choose Admin Panel, then Settings, and tick Allow signup only using invite url. It is saved at once.
The free edition has no two-factor authentication, so give every account a strong password. Close the tunnel when you are done.
NocoDB's documentation also lists NC_INVITE_ONLY_SIGNUP=true for this. In our tests on 2026.09 it had no effect: a stranger could still sign up until the box was ticked.
5. Put Caddy in front
Go back to root with exit, switch to machinectl shell caddy@, and add this block at the end of ~/Caddyfile:
nocodb.example.com {
header Content-Security-Policy "script-src 'self' 'unsafe-inline' 'unsafe-eval' blob:; frame-src 'self' blob:"
reverse_proxy 127.0.0.1:8116
}
Restart Caddy with systemctl --user restart caddy, and open https://nocodb.example.com. Log in with the account from step 4.
The header line stops browsers from loading scripts and frames from other sites. NocoDB's pages load the Chatwoot support chat from app.chatwoot.com in every visitor's browser, also with NC_DISABLE_SUPPORT_CHAT=true; with this line, the browser refuses it. Everything else in this guide worked with it.
To check that sign-up is closed, open https://nocodb.example.com/signup in a private window and try. It answers "Not allowed to signup, contact super admin."
6. Email
NocoDB sends email for invites and password resets. This guide's tests used an SMTP test server on the same machine; we did not send to real mailboxes. Without email:
- Invite User in the Admin Panel shows the invite link with "Looks like you have not configured mailer yet!", and you send the link yourself;
- Forgot your password ? on the login page says "Please check your email to reset the password", but sends nothing, and logs nothing.
To send email, point NocoDB at your email provider's SMTP server. As nocodb, store the password as a secret:
printf %s 'the-smtp-password' | podman secret create nocodb-smtp-password -
Then add these lines to nocodb-app.container, under the other Secret= lines, with your own server, account and sender address:
Environment=NC_SMTP_HOST=mail.example.com NC_SMTP_PORT=587 NC_SMTP_SECURE=false
Environment=NC_SMTP_USERNAME=nocodb@example.com NC_SMTP_FROM=nocodb@example.com
Secret=nocodb-smtp-password,type=env,target=NC_SMTP_PASSWORD
Restart NocoDB:
systemctl --user daemon-reload
systemctl --user restart nocodb-app
On port 587, NC_SMTP_SECURE=false starts without TLS and switches to it with STARTTLS when the server offers it, as providers do. For port 465, which uses TLS from the start, use NC_SMTP_PORT=465 NC_SMTP_SECURE=true. Admin Panel > Setup then shows Configure E-mail with a green tick, and Forgot your password ? sends a "Reset your password" email.
7. Invite your team
Choose Invite Members in the sidebar, then Add Members. Enter one or more email addresses, pick a role in the list next to them, and choose Invite to Workspace:
- Creator can create and change bases, tables and fields.
- Editor can add, change and delete records, but not change tables.
- Commenter can read and comment, Viewer can only read.
Each person gets an email, "You've been invited to a Workspace", with Accept Workspace Invite, a link to https://nocodb.example.com/signup/…. There they sign up with that address and a password of their own, and see the bases at once.
Without email, use the Admin Panel instead: User Management > Invite User shows the link to copy and send yourself. Someone invited that way sees "You haven't been added to any workspace yet" until you give them a role: under Invite Members, change No Access next to them to a role, and Confirm.
8. A base with tables and links
A base is a database, with tables in it. To start:
- On the Bases page, choose New Base, name it, for example
Office, and Create Base. - Choose Create New Table, name it
Customers, and press Enter. The table starts with a Title field. - Choose + at the end of the column headers. Give the field a name, such as
Email, pick the Email type from the list, and Save Field. AddPhonethe same way, as a Phone number. - In the sidebar, Create New > Table adds a second table,
Projects. Give itStatusas a Single select with the optionsIdea,DoingandDone,Dueas a Date,Budgetas Currency,Paidas a Checkbox andFilesas an Attachment.
To link each project to a customer, add a field Customer to Projects with the type Link to another record. Choose Many to One, since a project has one customer and a customer can have many projects, pick Customers in select table to link, and Save Field. A cell in Customer now offers the records of Customers, and each customer gets a field that lists their projects.
Add records with New record at the bottom of a table, or type in the empty row. The arrow at the start of a row opens the whole record, with a comment field next to it.
A Formula field calculates a value from other fields. Comparing a date with the current time does not work in 2026.09.1: {Deadline} > NOW() gave true for past and future deadlines alike. DATETIME_DIFF({Deadline}, NOW(), 'days') > 0 gave the right answer.
9. Kanban, form and other views
Each table opens in a grid view. To add another view, hover over the table in the sidebar and choose its +:
- Kanban asks for Stack by field; with
Status, each option becomes a column and cards can be dragged between them. - Form makes a form from the table's fields, with a title and description you can edit. Remove the fields that people outside should not fill in, such as
BudgetandPaid. - Gallery, Calendar and Map are in the free edition too; List, Timeline and Gantt need a paid licence.
Fields, Filter, Group and Sort above a view change only that view, so a team can keep a grid of everything next to a view of "my open projects".
10. Share a view and a form
Share at the top right of a view, then Enable Public Viewing, makes a link that works without a login:
- For a grid, kanban or gallery view, the link shows the view read-only, with the fields the view shows. In our test, typing in a cell changed nothing. Restrict access with password adds a password, and Allow Download, which is on by default, lets visitors download the rows as CSV, JSON or Excel; switch it off if they should only look.
- For a form view, the link at
https://nocodb.example.com/nc/form/…lets anyone send a record. A test record from a private window appeared in the table at once.
Switch Enable Public Viewing off to stop a link. Share Base in the same dialog shares a whole base; leave it off unless you mean it.
11. Attachments
An Attachment field takes files: open a record and choose Click to browse files, or drag a file there, then Upload 1 files. Each file can be up to 20 MB, and a cell holds up to 10 files. NC_ATTACHMENT_FIELD_SIZE and NC_MAX_ATTACHMENTS_ALLOWED in nocodb-app.container change that.
The files go in the nocodb-data volume, under nc/uploads. With NC_SECURE_ATTACHMENTS=true, a file's fixed address answers 404, and NocoDB hands out links that stop working after about two hours, also in shared views.
To keep files in S3 storage instead, such as Garage on another server, use Admin Panel > Setup > Configure Storage, or the NC_S3_ settings in NocoDB's documentation. We did not test that.
12. API tokens and the REST API
Every table can be read and written over a REST API. Open your account menu, Account Settings > API Tokens > Create new token, give it a name, such as scripts, and Create token. The token, starting with nc_pat_, is shown once. It has the same rights as your account, so keep it like a password.
The address of a table in your browser holds the IDs the API needs: in https://nocodb.example.com/w35ooeyj/pbp18c1bsz41gj7/mjsj3tt04guwuig/…, the part after the workspace that starts with p is the base, and the next part, starting with m, the table. To list the projects, from any computer:
curl -s "https://nocodb.example.com/api/v3/data/pbp18c1bsz41gj7/mjsj3tt04guwuig/records?fields=Title,Status" \
-H "xc-token: nc_pat_..."
The answer is JSON with a records list. To add a project:
curl -s -X POST "https://nocodb.example.com/api/v3/data/pbp18c1bsz41gj7/mjsj3tt04guwuig/records" \
-H "xc-token: nc_pat_..." -H 'Content-Type: application/json' \
-d '{"fields": {"Title": "Annual report", "Status": "Idea"}}'
Details > API Snippets above a table shows ready-made examples for that table. Delete a token you no longer use under API Tokens.
Webhooks (step 13) can be created over the API too, at /api/v2/meta/tables/…/hooks. Give them the body the interface adds by itself, "body": "{{ json event }}" in notification.payload. Without it, NocoDB sent an empty POST in our test, and the webhook's log said EMPTY_BODY.
13. Webhooks
A webhook calls an address of yours when records change, for example a workflow in n8n. Above a table, choose Details > Webhooks > New Webhook. Choose the event, such as After insert, or keep Send all events, enter the address after POST, and Create Webhook. Test Webhook sends a sample, and shows the error if it fails.
Each call is a POST with JSON, such as "type": "records.after.insert" and the new rows under data. In our test, it arrived within a second of a record being added.
NocoDB refuses webhook addresses on private networks, such as localhost and 192.168.x.x, so that a webhook cannot be used to reach services inside your network. An address on the internet, such as https://n8n.example.com/webhook/…, works.
If n8n runs on this same server, the call fails with connect ECONNREFUSED, and an AddHost line does not help, as NocoDB refuses that private address too. Give the nocodb user the containers.conf from the Podman guide, step 8, and restart the pod with systemctl --user restart nocodb-pod.
14. Use NocoDB in Swedish
NocoDB comes with a Swedish interface, translated by AI, which shows in a few odd words: bases are called "Projekt". Each user picks Svenska in the language menu at the top right of Account Settings. The names of your own tables and fields stay as you wrote them.
15. Outside connections
We captured the server's DNS lookups and outgoing connections while NocoDB started, while users were invited, and while the steps above were done. Apart from Docker Hub, when images were downloaded, one name came up: api.github.com. When someone opens NocoDB, the server asks GitHub for the newest release, to show Upgrade available at the top of Account Settings. We found no setting that turns it off.
In the browser, NocoDB's pages load a script from app.chatwoot.com, a support chat run by Chatwoot, Inc., even with the chat switched off. The header line from step 5 blocks it. With NC_DISABLE_TELE, NC_DISABLE_ERR_REPORTS and NC_DISABLE_SUPPORT_CHAT, NocoDB sent no usage statistics or error reports in our capture. teleEnabled and errorReportingEnabled are both false in https://nocodb.example.com/api/v1/meta/nocodb/info when you are logged in.
Webhooks call the addresses you give them, and the AI features, if you buy them, use the services you set up.
Back up
As nocodb:
umask 077
mkdir -p ~/backup
podman exec nocodb-db pg_dump -U nocodb -Fc nocodb > ~/backup/nocodb-db.dump
podman volume export nocodb-data > ~/backup/nocodb-data.tar
podman secret inspect --showsecret --format '{{.SecretData}}' nocodb-jwt-secret > ~/backup/jwt-secret.txt
The database holds the tables, records, users, views, shared links, API tokens and webhooks; the volume holds the uploaded files. The dump runs while NocoDB keeps working. Copy ~/backup to another machine, for example with restic, and keep it private: it holds all your data.
To restore into an empty pod, as nocodb, with the backup in ~/backup:
systemctl --user stop nocodb-pod
podman volume rm nocodb-db nocodb-data
podman volume create nocodb-data
podman volume import nocodb-data ~/backup/nocodb-data.tar
systemctl --user start nocodb-db
podman exec -i nocodb-db pg_restore -U nocodb -d nocodb --no-owner < ~/backup/nocodb-db.dump
systemctl --user start nocodb-app
Starting only the database first loads the backup before NocoDB sets up an empty one. After our restore, the records, files, users and API token all worked. On a new server, create the login key from the backup instead of a new one in step 2, so that nobody is logged out:
tr -d '\n' < ~/backup/jwt-secret.txt | podman secret create nocodb-jwt-secret -
Update
NocoDB publishes a release every two or three weeks, listed on its GitHub page with notes about what changed. To update, as nocodb, back up first, then change the version in the Image= line of nocodb-app.container, for example to 2026.10.0, and restart:
systemctl --user daemon-reload
systemctl --user restart nocodb-app
podman image prune -a -f
NocoDB updates its database when it starts. podman image prune -a removes the old image, which takes about 900 MB; it keeps the images your containers use. Without -a, it leaves the old image, as it still has its version tag. We updated from 2026.09.0 to 2026.09.1 this way, and the data and logins were kept.
PostgreSQL updates itself within version 17. Moving to a newer major version of PostgreSQL needs a dump and restore, as in Back up.
Troubleshooting
curl prints Cannot GET /api/v1/health. NocoDB is still starting. Wait a minute; podman logs nocodb-app ends with "App started successfully." when it is ready.
Strangers can sign up. Allow signup only using invite url is not ticked, step 4. Remove accounts you do not know under Admin Panel > User Management.
A new user sees "You haven't been added to any workspace yet". They have no role yet. Give them one under Invite Members, step 7.
"Please check your email to reset the password", but no email comes. Email is not set up, step 6. The super admin can still invite people with a copied link.
Everyone is logged out after a restart. The Secret=nocodb-jwt-secret line is missing, so NocoDB makes a new key each time it starts.
A webhook is never called. Test Webhook shows why. "Connection to a private network IP is blocked for security reasons" means its address is on a private network, which NocoDB refuses (step 13). connect ECONNREFUSED for an app on this server means the nocodb user lacks the containers.conf that step 13 describes.