Service Bindings
Service bindings are used to give applications access to endpoint credentials. Postgres and MySQL databases are currently supported by OpenRun. The administrator creates a service with connection information for the database. Apps can easily get access to an isolated database/schema without any manual configuration being required. OpenRun uses the admin credentials to create binding accounts for applications. Apps can share access to a schema, with support for granting limited permissions across applications.
Service bindings are an easy way to configure one database installation properly (with backups, fault tolerance, security etc) and then safely share that database across multiple apps. This is an alternate approach as against usual deployment tooling where each app is assumed to create its own database from scratch, which ignores the challenges with ensuring that the database is properly administered.
The currently supported service types are:
| Service type | Purpose |
|---|---|
postgres | Create Postgres schemas and roles |
mysql | Create MySQL databases and users |
Concepts
A Service is an admin connection to an endpoint (database). Apps do not use this connection directly. OpenRun uses the service connection to create database users and apply grants.
A Base Binding is created from a service. It creates the main database account for an app. For Postgres, this creates a schema and role. For MySQL, this creates a database and user.
A Derived Binding is created from a base binding. It uses the same schema or database as the base binding, but gets a separate account. Grants on the derived binding control what that derived account can do.
Bindings always have a staging and a prod environment. Grant related binding changes are applied to the staged binding first. Use binding update --promote to promote the staged grants to prod. Staging apps are bound to the stage binding env and prod apps are bound to the prod env. This gives an easy way to ensure that the staging app has access to an isolated test environment which is very similar to the prod env.
Create Services
Use openrun service create to create a service. The service id is <service_type>/<service_name>.
openrun service create postgres/main \
--is-default \
--config url=postgres://admin:secret@db.example.com:5432/appdb
openrun service create mysql/main \
--is-default \
--config url=mysql://admin:secret@db.example.com:3306/The first service of a type is automatically marked as default. Use --is-default to explicitly mark a service as the default. When creating a binding, the source can be the full service id like postgres/main, or just the service type like postgres. If only the service type is specified, OpenRun uses the default service for that type.
openrun binding create postgres /apps/reporting-dbList services with:
openrun service list
openrun service list postgres
openrun service list postgres/mainUpdate service default status with:
openrun service update postgres/main --set-default=trueDelete a service with:
openrun service delete postgres/mainHostname Mapping Basics
Service connection URLs are used in two different places: OpenRun uses the admin URL to create and manage binding accounts, and app containers use generated account URLs to connect at runtime. These hostnames can be different.
Generated binding accounts include both url and url_direct. url is intended for app containers and can replace the service URL hostname using the service config key binding_hostname. url_direct keeps the original service URL hostname and is used by OpenRun commands such as binding run-command.
This matters for local database services. If a Postgres or MySQL service URL points at localhost or 127.0.0.1, app containers usually cannot use that hostname to reach the host database. Outside Kubernetes, OpenRun automatically maps those local hostnames to a container-reachable hostname. Docker uses host.docker.internal; other local container runtimes use host.containers.internal. Set binding_hostname explicitly to override that value, or set binding_hostname=disable to keep the service URL hostname unchanged.
Staging Services
A service can specify a separate staging service. The staging service has to be of the same service type. When a binding is created, OpenRun creates the staged account using the staging service and the prod account using the main service. Create the staging service first, then reference it from the main service:
openrun service create postgres/stage \
--config url=postgres://admin:secret@stage-db.example.com:5432/appdb
openrun service create postgres/main \
--is-default \
--staging stage \
--config url=postgres://admin:secret@prod-db.example.com:5432/appdbYou can add, change, or clear the staging service later:
openrun service update postgres/main --staging stage
openrun service update postgres/main --staging ""The staging service cannot refer to itself. If no staging service is linked, then stage bindings are created on the same endpoint as the prod, just a separate schema/database. Stage performance issues can impact prod in that case.
Create Base Bindings
Create a base binding using a service source:
openrun binding create postgres/main /apps/reporting-db
openrun binding create mysql/main /apps/inventory-dbBase bindings cannot have grants. The generated account owns the bindings schema or database.
The account information is not shown by binding get or binding list. Use
binding show-account to view the generated connection information.
openrun binding show-account /apps/reporting-db
openrun binding show-account --staging /apps/reporting-dbFor testing, SQL can be run as the binding account. Output can be truncated for large results sets.
openrun binding run-command /apps/reporting-db "select current_user"
openrun binding run-command --staging /apps/reporting-db "select current_user"Create Derived Bindings
Create a derived binding by using a base binding path as the source.
openrun binding create --grant "read:*" /apps/reporting-db /apps/reporting-read
openrun binding create --grant "create:*" /apps/reporting-db /apps/reporting-writer
openrun binding create --grant "full:events" /apps/reporting-db /apps/reporting-events-adminDerived bindings have to be created from base bindings. A derived binding cannot be used as the source for another derived binding.
Grants are supported only on derived bindings. A grant is specified as type:target.
| Grant | Meaning |
|---|---|
read:* | Read all tables |
read:<table> | Read one table. If the table does not exist yet, the grant is deferred. |
create:* | Create tables |
full:* | Read, write and create |
full:<table> | Read and write one table |
create:<table> is not supported. Create access applies to the schema or database.
If a table-specific grant references a table which does not exist yet, the grant is kept in the metadata and will be applied later on next update call or using --reapply-all.
Update and Promote Grants
Grant updates are staged. The update is applied to the staged account first.
openrun binding update --add-grant "read:*" /apps/reporting-read
openrun binding update --delete-grant "read:old_table" /apps/reporting-readProd is not updated until the binding is promoted:
openrun binding update --promote /apps/reporting-readYou can update and promote in one command:
openrun binding update \
--add-grant "read:*" \
--delete-grant "read:old_table" \
--promote \
/apps/reporting-readUse --reapply-all to apply all grants again. This is useful after creating a table for which a table-specific grant was previously deferred, or after manual database changes.
openrun binding update --reapply-all --promote /apps/reporting-readBinding promotion is separate from app promotion. app promote promotes the app version and app metadata, including the list of binding paths attached to the app. It does not promote staged grant changes inside a binding. Use binding update --promote or apply --promote for that.
Attach Bindings to Apps
Attach existing bindings when creating an app:
openrun app create \
--bind /apps/reporting-db \
github.com/example/reporting-app \
/reportingBinding order is preserved. To update the binding list for an existing app:
openrun app update bindings /apps/reporting-read /apps/metrics-read /reportingThis updates staging. Add --promote to update prod in the same command.
Binding Source Permissions
Apps can only use bindings whose source is allowed by the app metadata or by the system-level permissions.binding_source_perms setting in openrun.toml. The --bind-perm option records the binding sources an app is requesting permission to use. If --approve is also used during app creation, the requested binding source permissions are copied into the app’s approved list.
openrun app create \
--bind-perm postgres/main \
--approve \
github.com/example/reporting-app \
/reporting
openrun app update bind-perm postgres/main /reporting
openrun app approve --promote /reportingRuntime binding access is checked against the approved binding source permissions. Updating bind-perm on an existing app stages the requested source list, but it does not approve it. Run openrun app approve after the update, or use openrun apply --approve for declarative updates, so the new binding sources are approved before the app uses them.
By default at the system level, bindings are allowed to the default postgres and mysql services. This can be configured by updating openrun.toml:
[permissions]
binding_source_perms = ["postgres", "mysql"] # default postgres and mysql binding sources are allowed by defaultFor declarative apply files, use bind_perm in the app definition:
app(
"/reporting",
"github.com/example/reporting-app",
bindings=["/apps/reporting-db"],
bind_perm=["postgres/main"],
)Auto Bindings
When the value passed to --bind starts with /, OpenRun treats it as an existing binding path. When it does not start with /, OpenRun treats it as a service source and creates a base binding automatically if the binding does not already exist.
openrun app create \
--bind postgres/main \
--bind-perm postgres/main \
--approve \
github.com/example/reporting-app \
/reportingThe generated binding is stored under:
/auto/<main-app-id>/<service-type>For example, a Postgres auto binding is stored as /auto/app_prd_.../postgres. Duplicate service references in the same command resolve to one auto binding.
The /auto path is reserved for auto bindings. Users cannot create bindings under that path directly. A derived binding can use an auto binding path as its source.
Declarative Apply
Apply files can define bindings using the binding builtin.
binding("/apps/reporting-db", "postgres/main", config={"inherit_default": "false"})
binding("/apps/reporting-read", "/apps/reporting-db", grants=["read:*"])
app("/reporting", "github.com/example/reporting-app", bindings=["/apps/reporting-read"])The builtin format is:
binding(path, source, grants=[], config={})| Property | Optional | Type | Default | Notes |
|---|---|---|---|---|
| path | False | string | The unique path for the binding | |
| source | False | string | The source for binding, service or based binding path | |
| grants | true | string array | The permission grants for a derived binding | |
| config | true | dict | The config map |
The source rules are the same as the CLI:
source="postgres/main"orsource="postgres"creates a base binding.source="/apps/reporting-db"creates a derived binding.grantsis valid only for derived bindings.configis used only when the binding is first created.
openrun apply creates bindings even if the app glob does not match any apps. Existing binding sources and binding config cannot be changed.
For existing bindings, apply does a three-way merge for grants. Grant changes in the apply file are applied, and grant changes made using the CLI are preserved. Use --clobber to make the staged grants match the apply file.
openrun apply --reload=none apps.ace /reporting
openrun apply --promote --reload=none apps.ace /reportingWith --promote, apply promotes binding metadata after updating staged metadata.
Postgres Config and Behavior
Postgres services require url. They also support binding_hostname.
| Key | Required | Description |
|---|---|---|
url | Yes | Admin Postgres connection URL |
binding_hostname | No | Hostname to substitute into generated url account URLs. url_direct keeps the original service URL hostname. If omitted for a localhost or 127.0.0.1 service URL outside Kubernetes, OpenRun automatically uses a container-reachable host name. Set to disable to keep url unchanged and skip automatic mapping |
For example:
openrun service create postgres/main \
--config url=postgres://admin:secret@localhost:5432/appdbThe admin user in the URL must be able to create roles, create schemas, grant privileges and alter default privileges.
Postgres bindings support one create-time binding config key:
| Key | Default | Description |
|---|---|---|
inherit_default | true | Whether the generated role inherits privileges from other roles, including PUBLIC |
For example:
openrun binding create \
--config inherit_default=false \
postgres/main \
/apps/reporting-dbIf inherit_default is set to false, the generated role is created with NOINHERIT.
For a base binding, OpenRun creates a schema and a login role. The generated account includes url and url_direct. url uses the service URL with the generated username and password, replacing the hostname with binding_hostname when that service option is set. If binding_hostname is omitted, the service URL hostname is localhost or 127.0.0.1, and OpenRun is not running in Kubernetes mode, OpenRun automatically uses host.docker.internal for Docker and host.containers.internal for other local container runtimes. Set binding_hostname=disable to opt out of both explicit hostname substitution and automatic mapping. url_direct uses the original service URL hostname. Containers receive both values as environment variables, for example POSTGRES_URL and POSTGRES_URL_DIRECT. binding run-command uses url_direct. OpenRun sets the generated role’s default search_path to the binding schema.
For a derived binding, OpenRun creates a separate login role and uses the base binding schema. The derived role gets USAGE on the schema before grants are applied.
Postgres grants work as follows:
read:*grantsSELECTon all current tables and changes default privileges so future tables created by the base role are readable by the derived role.create:*grantsCREATEon the schema.full:*grants all table privileges, all sequence privileges andCREATEon the schema. Default privileges are also updated for future tables and sequences.read:<table>andfull:<table>apply only to the specified table.
If a table-specific grant references a table which does not exist, OpenRun skips the grant for that run. Skipped grants are applied on the next update/apply run.
MySQL Config and Behavior
MySQL services require url. They also support host_pattern and binding_hostname.
| Key | Required | Description |
|---|---|---|
url | Yes | Admin MySQL URL |
host_pattern | No | Host part for generated MySQL users. Defaults to % |
binding_hostname | No | Hostname to substitute into generated url account URLs. url_direct keeps the original service URL hostname. If omitted for a localhost or 127.0.0.1 service URL outside Kubernetes, OpenRun automatically uses a container-reachable host name. Set to disable to keep url unchanged and skip automatic mapping |
For example:
openrun service create mysql/main \
--config url=mysql://admin:secret@localhost:3306/ \
--config host_pattern=10.0.%The admin user in the URL must be able to create users, create databases, and grant and revoke privileges.
For a base binding, OpenRun creates a database and user. The base user gets ALL PRIVILEGES on the generated database. The generated account includes url and url_direct; url uses binding_hostname when that service option is set. If binding_hostname is omitted, the service URL hostname is localhost or 127.0.0.1, and OpenRun is not running in Kubernetes mode, OpenRun automatically uses host.docker.internal for Docker and host.containers.internal for other local container runtimes. Set binding_hostname=disable to opt out of both explicit hostname substitution and automatic mapping. url_direct keeps the original service URL hostname. Containers receive both values as environment variables, for example MYSQL_URL and MYSQL_URL_DIRECT. binding run-command uses url_direct.
For a derived binding, OpenRun creates a separate user and uses the base binding database. The derived user gets a minimal database-level SHOW VIEW grant so it can connect using the generated database as the default database.
MySQL grants work as follows:
read:*grantsSELECTon the database. This applies to current and future tables.create:*grantsCREATE,ALTER,INDEX,DROP, andREFERENCESon the database.full:*grants read, write, create, alter, index, drop, references, trigger, create view, temporary table and lock privileges on the database.read:<table>andfull:<table>apply only to the specified table.
If a table-specific grant references a table which does not exist, OpenRun skips the grant for that run. Skipped grants are applied on the next update/apply run.
MySQL DDL statements auto-commit. If binding creation fails part way through, OpenRun does best-effort cleanup for users and databases created during that operation.