Databases
Databases
A database is a managed MariaDB database that belongs to your account, not to one app. You create it under Storage → Databases (/account/databases), give it users, and connect it to one or more of your apps. When you connect an app, ngris gives it the connection details as environment variables (DB_HOST … DATABASE_URL), so you never copy a password into your app.
- MariaDB only. It works with MySQL drivers and tools. Postgres, Redis and other engines aren't available. If you need one, run it elsewhere and set its URL as a secret environment variable.
- Its own users. Each database has up to 10 users, each read-write or read-only. A password is shown once, when the user is created or its password is reset.
- Only apps on ngris connect. Managed, full-stack and function apps on ngris can connect to a database. Nothing outside ngris can — see Who can connect.
- Team and Enterprise plans. Team includes 3 databases, Enterprise 10 — see Plans and limits.
Create a database
Open Storage → Databases and click New database (or Create → Database in the top bar). The wizard has three steps:
- Name. A label only you see: 1–64 characters, no control characters, unique in the account. You can rename it later. Inside MariaDB the database gets its own name:
app_, your name in lowercase letters, digits and_, then_and 8 generated characters (for exampleapp_orders_db_k3x9p2ab). That name never changes. - Engine. MariaDB.
- Review. Check the details and click Create database.
ngris creates the database with a first user named app (read-write). The database's page opens and shows that user's login and password once: copy the password then, because a reload doesn't show it again. Apps you connect as app get the password automatically, so you only need it for your own records. If you lose it, reset the password.
The status is Creating (provisioning) for a few seconds, then Ready (ready). The page updates by itself. If creation fails (failed), nothing is left half-made: delete the database in Settings and create a new one.
API
curl -X POST "https://api.ngris.com/v1/databases" \
-H "X-API-KEY: <your_api_key>" \
-H "Content-Type: application/json" \
-d '{"name":"orders-db"}'
The 201 response carries the database, its first user and that user's password, which the API never returns again. Pass initial_user_name to name the first user something other than app. See Create Database and the rest of the Databases API.
Users
Apps connect to a database as one of its users. Manage them on the database's Users tab. A database can have up to 10 users.
- New user. Give it a name (1–32 characters, not starting with
_, unique on this database) and pick its access. ngris generates the login, likeu_k3x9p2_reporting, and a 48-character password, shown once in the dialog. - Reset password. Gives the user a new password, shown once. Apps connected as the user restart to pick it up; until they have restarted, their new database connections fail. (In the API this is rotate.)
- Delete user. Refused while any app connects as the user: disconnect the app, or connect it as another user, first.
Passwords are stored encrypted and never shown again after the dialog closes. Apps connected as a user always get its current password automatically.
Access
| Access | Privileges on its database | Use it for |
|---|---|---|
| Read-write (default) | SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, ALTER, INDEX, CREATE VIEW, SHOW VIEW | Apps that change data and run their own migrations. |
| Read-only | SELECT, SHOW VIEW | Reporting and analytics apps. |
A user's privileges cover its own database only. Several apps can connect as the same user, but they then share that user's connection and query-rate caps. Give each app its own user, so you can reset or remove one without touching the others.
Connect an app
Open the app's Environment tab. The Connected databases card lists the databases the app uses; click Connect database and fill in:
- Database — one of your ready databases. Create a new database opens the wizard and brings you back here when it's ready.
- Connects as — a ready user of that database.
- Prefix (optional) — leave it empty for
DB_HOST,DB_PORT, …; add one to connect more than one database to the app. - Use in — Both (production and preview, the default), Production only or Preview only. Preview covers branch previews and not-yet-promoted deploys.
The dialog previews the variables before you save. You can also start from the database: its Connected apps tab has Connect an app, which opens that app's Environment tab with the database chosen.
The variables an app gets
| Variable | Value |
|---|---|
{PREFIX}DB_HOST | The database server's internal address (shown on the database's Connection tab). |
{PREFIX}DB_PORT | The port, 3306. |
{PREFIX}DB_NAME | The database's name inside MariaDB (app_…). |
{PREFIX}DB_USER | The user's login (u_…). |
{PREFIX}DB_PASSWORD | The user's password. Secret. |
{PREFIX}DATABASE_URL | mysql://<user>:<password>@<host>:<port>/<database>. Secret. |
With no prefix the names are exactly DB_HOST, DB_PORT, DB_NAME, DB_USER, DB_PASSWORD and DATABASE_URL. A prefix is uppercased and gets a trailing _ if it lacks one, so orders becomes ORDERS_ and the app gets ORDERS_DB_HOST … ORDERS_DATABASE_URL. A prefix starts with a letter, has up to 30 more letters, digits or _, and ends in _.
What happens when you connect
- The app restarts. If the app is running, saving restarts it so it picks up the variables. If it isn't running yet, it gets them on its next deploy.
- The password stays out of your variables. ngris builds the variables from the connection when it starts the app; the password is never stored among the app's environment variables.
- Locked rows. The Environment tab lists the variables with a lock and a database label;
DB_PASSWORDandDATABASE_URLare masked. You can't edit or delete them there: use Open database or Disconnect. A row marked pending reaches the app once the database and its user are ready; ngris then restarts the app again. - The names are reserved. Connecting is refused if the app already uses one of the names, as a variable you set or through another connection, in an overlapping environment (Both overlaps everything). The error lists the names; remove them or choose a prefix. While the connection exists, you can't set a variable with one of its names. (Variable names starting with
DB_,DDL_orWORDPRESS_DB_, andDATABASE_URL, are reserved by ngris on backend apps in any case.)
Disconnect (on the Connected databases card, a locked variable row, or the database's Connected apps tab) removes the variables from the app and restarts it if it's running. The database and its users stay.
API
curl -X POST "https://api.ngris.com/v1/applications/{uuid}/database-connections" \
-H "X-API-KEY: <your_api_key>" \
-H "Content-Type: application/json" \
-d '{"database_uuid":"<database uuid>","user_uuid":"<user uuid>","env_prefix":"orders","environment":"all"}'
See Database Connections. The API also accepts a database or user that is still being created: the response then says env_pending: true, and the variables arrive once both are ready.
Use the connection in your code
Read the variables from the environment when your app starts; never hard-code the password. The examples use the names without a prefix; with a prefix such as ORDERS_, read ORDERS_DATABASE_URL, ORDERS_DB_HOST and so on.
Node (mysql2 / Prisma)
Use DATABASE_URL directly — both accept the mysql:// URL as-is.
// mysql2
import mysql from "mysql2/promise";
const db = await mysql.createConnection(process.env.DATABASE_URL);
// Prisma (schema.prisma)
datasource db {
provider = "mysql"
url = env("DATABASE_URL")
}
Python (SQLAlchemy)
SQLAlchemy needs a driver in the URL scheme (e.g. mysql+pymysql://). Either swap the scheme on DATABASE_URL, or build the URL from the parts so the scheme is explicit:
import os
from sqlalchemy import create_engine
url = (
f"mysql+pymysql://{os.environ['DB_USER']}:{os.environ['DB_PASSWORD']}"
f"@{os.environ['DB_HOST']}:{os.environ['DB_PORT']}/{os.environ['DB_NAME']}"
)
engine = create_engine(url)
Go (go-sql-driver/mysql)
mysql:// URL. go-sql-driver/mysql wants a DSN in the form user:pass@tcp(host:port)/dbname, not a URL. Passing DATABASE_URL to sql.Open is a common, silent failure — build the DSN from the DB_* variables instead.
import (
"database/sql"
"fmt"
"os"
_ "github.com/go-sql-driver/mysql"
)
dsn := fmt.Sprintf(
"%s:%s@tcp(%s:%s)/%s?parseTime=true&charset=utf8mb4",
os.Getenv("DB_USER"), os.Getenv("DB_PASSWORD"),
os.Getenv("DB_HOST"), os.Getenv("DB_PORT"), os.Getenv("DB_NAME"),
)
db, err := sql.Open("mysql", dsn)
The parseTime=true&charset=utf8mb4 query keeps DATETIME/TIMESTAMP columns scanning into time.Time and gives you full Unicode (including emoji).
Migrations and seed data
A new database is empty; your app owns the schema. Run migrations when the app starts (for example Prisma migrate deploy, an ORM auto-migrate, or a golang-migrate step in your entrypoint), before the server accepts traffic. The build can't reach the database, so migrations can't run during the build. A read-write user has the privileges migrations need (CREATE, ALTER, INDEX, DROP, views).
Who can connect
Only your apps that run on ngris can reach a database: managed (backend), full-stack and function apps. The database server has an internal address only. There is no outside access:
- not from your computer, CI or another cloud;
- not from a service behind an agent tunnel;
- not from edge functions;
- not from the build (run migrations when the app starts).
Static sites can't use a database: they run in the visitor's browser, with no server to connect from. WordPress sites use their own built-in database; other databases can't be connected to them.
Plans and limits
| Plan | Databases | Storage per database |
|---|---|---|
| Developer (free) | Not included | — |
| Team | 3 | 1 GiB |
| Enterprise | 10 | 2 GiB |
Pay-as-you-go plans don't include databases either. The Databases page shows how many you use (“2 of 3”). At the limit, New database is disabled and the API answers 403 “You've reached your plan's limit of 3 managed databases — delete one or upgrade your plan”. A database that failed or is being deleted doesn't count.
- Users: up to 10 per database, on every plan that includes databases.
- Storage: ngris doesn't measure database sizes yet. The database's Overview tab shows Measured soon.
Paused databases
When your account has more databases than its plan allows (for example after a downgrade), the extra ones are paused. A paused database's settings are read-only: you can't rename it, add users, reset passwords or connect apps (the API answers 409 with the code quota_paused). You can still delete the database or its users, and disconnect apps. Upgrade your plan, or delete a database you don't need, to make room.
Delete a database
Open the database's Settings tab and click Delete database, then type DELETE to confirm. ngris deletes the database, its users and all of its data. This can't be undone.
Deleting is refused while any app uses the database: the Settings tab lists the apps, and the API answers 409 database_in_use with them. Disconnect each app first.
When you delete an app
Deleting an app removes its connections. Every database it used stays, with its users and data — one you created under Storage → Databases, and also a per-app database added from the app's own Database tab — and you can connect it to another app or delete it. A database that stays still counts toward your plan's database limit until you delete it. Only a WordPress site's own database is deleted with the site.
The app's backups are deleted with the app, including the backups of a database that stays.
Per-app databases (an app's Database tab)
A managed, full-stack or function app can still add a database from its own Database tab (Add database, or POST /v1/applications/{uuid}/database). It's a shortcut: ngris creates an ordinary database under Storage → Databases, named after the app (with -2, -3 … when you already have a database of that name), with a read-write user named app, and connects the app to it as app with no prefix:
- the app gets
DB_HOST…DATABASE_URL, shown as locked database rows on its Environment tab — nothing is written into its own environment variables; - no password is shown when it's created: if you need one, reset the password of the
appuser (the app restarts); - it's an ordinary database: you can rename it, add users, connect other apps to it, or disconnect it;
- it stays when you delete the app, with its users and data;
- while the app is connected to it, the app's backups cover it.
An older per-app database that hasn't been moved onto a connection shows its app as connected (managed by the app): it can't be disconnected there, and it can't be deleted while the app exists.
A WordPress site gets its own database when it's created. That one belongs to the site: it's listed under Storage → Databases, doesn't count toward your plan's database limit, can't be disconnected, and is deleted with the site.
An app with a database from its Database tab already has the unprefixed names, so connect any other database to it with a prefix. App backups don't include databases created under Storage → Databases with New database, even when they're connected to the app.
Permissions
Account roles have a Databases permission (see named-role RBAC):
- Read — see the databases, their users and connections.
- Write — create databases and users.
- Update — rename a database, reset a user's password.
- Delete — delete databases and users.
Connecting a database to an app also changes the app, so it needs Endpoints permission too: listing an app's connections needs Databases read and Endpoints read; connecting needs Databases write and Endpoints write; disconnecting needs Databases delete and Endpoints write. The account owner has every permission.
Troubleshooting
- The database failed to create. Delete it in Settings and create a new one; no partial database is left behind.
- “These environment variable names are already used by the application”. The app already has one of the names, often
DB_HOST…DATABASE_URLfrom a database on its Database tab or from another connection. Connect with a prefix. - “This application type can't use a database”. Only managed, full-stack and function apps can connect. See Who can connect.
- The app can't connect. Almost always the wrong variable or format:
DATABASE_URL(amysql://URL) for Node, Prisma and SQLAlchemy, auser:pass@tcp(host:port)/dbDSN built from theDB_*variables for Go. Check the prefix too. See Use the connection in your code. - “Access denied”. Read the password from the environment at runtime; a password you copied or baked into the image stops working after a reset. Right after a reset, wait for the app's restart to finish.
- The variables are marked pending. The database or its user isn't ready yet. They reach the app once both are ready, and ngris restarts the app.
- You can't delete a user or the database. An app still uses it. Disconnect the app first (the error and the dialog list which).
Still stuck? See Deploy troubleshooting.