Skip to content

Application database bindings

An application binding gives one Container, Deployment, or eligible workload service private access to one managed database. Opfield creates a distinct engine user or role for the binding with application-level privileges on the managed database, never the owner or superuser credential. The engine identity belongs to the binding, not to a transient workload process.

Bindings exist only for managed databases. For a database that runs elsewhere and is registered as an external connection, store its connection values as workload secrets or Compose variables instead; see Managed and external databases at a glance.

Engine Binding identity
PostgreSQL A login role that is a member of the application role, with the application role as its default; no superuser, database, or role creation rights
ClickHouse A user whose role has all privileges on the application database and read access to the system tables Opfield needs; no access to other databases
Redis An ACL user limited to read, write, connection, transaction, publish/subscribe, and script-evaluation commands; dangerous and administrative commands are denied

Privileges are not chosen per binding: every binding on the same database receives the same application-level access through its own identity.

Use a binding when an application needs database access without sharing an owner credential or publishing the database to a broader network. The database owner approves which workloads receive access, the application owner chooses how connection values enter the workload, and the platform owner keeps the Storage and Docker Nodes available. Success means the application can perform its required work, cannot perform administrative work outside the application database, and keeps access after a controlled workload recreation without creating duplicate identities.

The main risk is credential or permission reuse. Create a separate binding for each workload boundary, keep the database private by default, and retire the binding when the workload no longer needs it.

Operator details: transport and ownership model

Section titled “Operator details: transport and ownership model”

From 2.11.1, every binding runs through the shared secure-link connector of the target Docker Node, the same one that serves storage links and container links. Each binding gets a small private network of its own, which the workload joins; the connector answers the binding’s host name there and carries each connection through the Relay to the database’s Storage Node. There is no per-binding connector container and no listener on the host. Opfield reconciles the link as part of the binding’s desired state. Link networks take their addresses from a dedicated range, described in Shared connector and link networks.

The link and the binding’s engine identity are separate but coordinated records. Each binding has its own database principal, so one workload’s credential rotation, deletion, or permission change does not silently change another workload’s access. Recreating a bound workload preserves the binding relationship and its distinct engine identity; Opfield reconciles the link and injects the current connection settings again.

Troubleshoot the binding’s link state and database readiness first; Ingress routes do not take part in a binding’s path.

Before creating a binding, confirm that the managed database is Ready, the target Docker node is online, the workload is eligible, and you have access to both resources: databases:edit on the database, and on the workload docker:containers:environment and docker:containers:secrets for a Container or Deployment, or docker:compose:manage for a Compose service. A binding change on a Deployment can roll it out, so it also needs docker:containers:manage on that Deployment, plus docker:containers:edit when it changes the Deployment’s environment. Review the intended database or role permissions and make sure the workload can consume the delivered connection settings without placing them in source control.

  1. Select the managed database and the target workload.
  2. Choose which connection values to deliver and the environment variable name for each.
  3. Create the binding and wait for Opfield to provision its engine identity and reconcile the link.
  4. Apply or recreate the workload only when its configuration change requires it.
  5. Verify a real application query, then inspect binding telemetry and workload and database logs.

Use private bindings for workload access even when direct TCP publication is enabled for an external client. Publication is opt-in and does not replace binding isolation.

Bindings can target an eligible standalone Container, Deployment, or Compose service on an online Docker Node, and the database must be Ready. Opfield delivers the values you select—connection URI, host, port, database, username, and password—under the environment variable names you choose; at least one value is required. The Console suggests names such as DATABASE_URL or REDIS_URL, or engine-specific names for individual values. The URI uses the engine’s plain protocol on the private binding network (postgresql://, redis://, or http:// for ClickHouse); bindings do not add TLS settings. The plain URI also works for PostgreSQL with TLS enabled: when a client connects without TLS, the daemon on the database’s Storage Node (2.11 or later) opens the TLS connection to PostgreSQL and verifies the instance’s certificate. A client that requests TLS itself, for example with sslmode=require, keeps its own end-to-end TLS session. Treat every delivered value as a secret even though the link itself is private. Do not copy the URI into Compose source, image layers, screenshots, logs, or Pages runtime configuration.

One database can serve multiple workloads through separate bindings. That is preferable to reusing one shared account: each binding can be audited, rotated, and retired independently. Opfield rejects conflicting environment-variable assignments rather than silently replacing an existing application value; the only exception is an explicit opt-in to replace existing values on a standalone Container.

The target workload does not have to run. You can create or delete a binding for a stopped, crash-looping, or failed Container, Deployment, or Compose service, and for one that has not been deployed yet. The request returns without waiting for the workload to start or become healthy, and a failed rollout does not undo the binding.

  • A binding that is saved in the workload’s configuration but not yet used by it shows as pending (observedState: "target_applied" in the API and MCP). It becomes active when the workload next starts or finishes its current rollout.
  • A crash-looping Container is recreated with the binding and keeps restarting under its restart policy.
  • A Deployment whose first Git build failed gets the binding when you retry the build; you do not need to set an image.
  • During a Git build rollout, bindings can still be saved; they reach the new revision when the rollout finishes.
  • Through the API, MCP, or the assistant, you can also bind a Git-source Container before its first build, by its name, and a Compose service before the project’s first revision or while a build rollout holds the project. The first build or the next revision starts the workload with the binding; if that revision does not define the service, the binding fails with the reason. The Console offers bindings for these workloads once the Container or the first revision exists.
  • A Deployment rollback, a slot switch, and applying an older Compose revision keep the current bindings. A revision that does not define a bound service cannot be applied until you delete that binding.
  • The target Docker Node must be online: a binding for a workload on an offline Node is refused with 409 NODE_OFFLINE.

After creation or workload recreation, verify all of the following:

  • the binding reports Ready, not pending, and its target workload is running;
  • the workload received the expected variable names without exposing their values;
  • an application query succeeds and an administrative operation outside the application database, such as creating a role or database, fails;
  • binding runtime telemetry shows the expected stream and no sustained admission rejects;
  • recreating the workload preserves the same binding relationship and does not create an additional engine role;
  • an unrelated workload cannot reach the binding’s private network or reuse the binding identity.

Container inspect, environment views, archive export, and duplicate do not reveal link passwords or storage link keys, and a duplicate does not inherit the source’s links. Credential reveal is an exceptional diagnostic action. It requires explicit access and should be followed by normal secret-handling controls; avoid revealing credentials merely to prove that a binding works when an application query and telemetry provide safer evidence.

A managed database link carries up to 64 concurrent connections, and so does a managed storage link. The capacity belongs to the link, not to a container: every container the link serves shares it, and during a blue/green rollout the old and the new slot share it until the old slot stops. Workload Availability gives each placement its own link. The engine’s own limit applies on top: a managed PostgreSQL instance keeps PostgreSQL’s default of 100 connections for all its links together.

Size the connection pools of one container at no more than half the capacity, 32, and count every pool in the process—ORM, job queue, migrations, and separate drivers—so that a new slot can open its pools while the serving slot still holds its own. S3 SDKs keep large keep-alive pools by default (50 sockets per client in the AWS SDK for JavaScript), so cap them as well.

The Node that runs the workload enforces the capacity for the link as a whole: its secure-link connector holds the link at 64 open connections whichever Relay of the pool carries each one, and every Relay caps the link’s route at the same 64. A connection over the capacity is accepted and closed at once, so clients report errors such as Connection terminated unexpectedly or ECONNRESET, and an S3 client of a storage link gets a connection error. The Node logs managed link connection rejected with reason=link_limit once a minute per link, with the count since the previous line; the line appears in the Node’s logs in Opfield, not only in the host journal.

The overview of a linked Container, Deployment, or Compose Project shows each link’s runtime: open connections against the limit of 64, throughput, connection setup latency, completion health, and admission rejects with the reason of the latest refusal. Storage links of Containers and Deployments appear next to database links, marked S3. The runtime of an Availability link is the sum of its placements.

Interface Database link Storage link
REST GET /api/databases/managed/{id}/bindings/{bindingId}/runtime GET /api/managed-storage/{id}/bindings/{bindingId}/runtime
MCP and assistant manage_managed_database get_binding_runtime manage_managed_storage get_binding_runtime

activeStreams is the number of open connections as the Node running the workload counts them, openedTotal and the byte counters are what the link carried through that Node whichever Relays carried it, throttledTotal counts connections refused at the capacity by the Node or a Relay, and connections adds the limit, the Node’s refusals, and the reason and time of the latest refused connection. Completions, failures, setup latency, and duration come from the Relays. connections is null while that Node runs a Docker daemon older than 2.11, and the other figures then come from the Relays as well. Sustained admission rejects mean the pools of the containers behind the link are too large.

Desired binding state remains durable through workload recreation, Docker daemon restart, node reconnection, and Relay restart. A link keeps its connections while Docker’s API is briefly slow and when Availability takes over or hands back a workload, and reconciliation runs only for the workload an event concerns. Reconciliation restores the link and confirms the engine identity without creating a new identity for every transient runtime. This is how Opfield avoids orphan roles after normal restart and recreation paths.

For a failed binding, diagnose in this order: managed database readiness; target Storage Node; target Docker node; binding desired state; the link on the Node’s secure-link connector; engine identity and permissions; then application configuration. Inspect the binding operation and telemetry before retrying. Do not delete the engine role, the link network, or the connector manually as a general repair tactic, because that can desynchronize the durable record from the database and daemon.

Delete the binding through Opfield when the workload no longer needs access. Opfield removes the link and retires the binding’s engine identity as part of one recorded lifecycle, preventing the normal deletion path from leaving unused identities behind. Delete the binding before deleting the database or the workload whenever possible, then verify that the link, telemetry, and engine principal cleanup have completed. Deleting a standalone Container through Opfield deletes its links as well, and so does giving its name to another container by creating, duplicating, renaming, or importing one, so a new container with the same name never receives the old link’s network or credentials.

If a deletion is interrupted, leave the durable binding record in place and use its operation state for recovery. Manual role deletion is an exceptional recovery procedure because it can make a future reconciliation fail; use it only with an explicit, coordinated recovery plan.

Operator details: upgrade and compatibility

Section titled “Operator details: upgrade and compatibility”

Bindings move to the shared connector when the target Node’s Docker daemon is updated to 2.11.1. Opfield recreates each bound workload once to attach it to its new link network, one workload at a time on each Node, the next once the previous runs healthy: a Deployment rolls out blue/green without downtime, while a standalone Container or a Compose service restarts once. Nothing needs to be done by hand, and the variable names and credentials stay the same. Rolling the Docker daemon back to 2.11.0 moves the bindings back automatically, again with one recreate per workload and at most four at once per Node, the daemon’s limit on concurrent commands; on a Node with many linked workloads, the later ones reconnect a little later. Do not delete old link networks or containers by hand during either move.

When the secure-link connector is updated, its open connections are kept for up to 30 minutes and then closed once; clients should reconnect with bounded retries. An application-only Opfield restart does not terminate established binding traffic while the Relay and target daemons stay healthy. A Relay update is a data-plane maintenance event and can interrupt streams. After a target Docker daemon or Node restart, verify both the binding state and a real query.