You configure the management port, the query port is discovered
Only the management port is stored, and the query endpoint comes from the cluster's own node services, preferring the external address it advertises.
A Couchbase cluster needs two endpoints: management and query. A connection record carries one port, so the second endpoint has to come from somewhere.
There is no second port field here. The management port is the one you type; the query endpoint is read from the cluster on the first statement. That is why a node reached through Docker port mapping or NAT connects without a field describing the mapping.
The fields a Couchbase connection actually needs
Four things, and one of them is not what its name says.
| Field | Required | What it carries |
|---|---|---|
host |
yes, or a connection string | A cluster node hostname |
port |
no | The management port. Defaults to 8091, or 18091 with TLS |
user / password |
no | Sent as HTTP Basic on every request |
database |
yes | The bucket name |
port is management only. The documented default is 8091, or 18091 when SSL is on.
Nothing on the form asks for 8093, because nothing on the form could reliably know
it.
database is the field that surprises people, so the form says so:
ConnectionModal renders the label “Bucket” when the connection type is
couchbase. The stored key is still database, because every provider shares one
connection shape, but what goes in it is a bucket name.
There is no driver underneath any of this. The provider speaks the documented
Query Service REST endpoint (/query/service) and the management API
(/pools/default...) using fetch for plaintext and the built-in node:https
for TLS. The official SDK was measured and rejected: 64.6 MB unpacked across 3765
files, with a postinstall step that downloads a prebuilt binary or compiles from
source. Studio ships as a Docker image, a Snap, an AppImage, a Flatpak, deb/rpm
packages and an npm package, and every one of those would have inherited the
native module.
Why the bucket field is required
A cluster is not a query target. The hierarchy is cluster, bucket, scope, collection, and the schema explorer renders a flat list of collections. The bucket is what pins that list to something finite.
So a connection without one is refused rather than guessed at, with this message:
Couchbase requires a bucket (use the "database" field)
Below the bucket, the flattening follows the rule PostgreSQL already established for schema and table. The default scope is implicit, everything else is qualified:
_default / hotel -> hotel
inventory / hotel -> inventory.hotel
Collections come from system:keyspaces LEFT JOIN system:scopes. The join is
LEFT for a specific reason: system:scopes does not list _default on Server
8.0.2, so an inner join silently drops every collection in the default scope.
Capella is where this bites. A Capella endpoint carries neither a port nor a
bucket path, so a pasted couchbases://cb.<id>.cloud.couchbase.com arrives with
the host and the SSL mode set and the bucket blank. It stays blank until you type
one; nothing is invented for it.
How the query port is discovered
The first statement on a connection triggers GET /pools/default/nodeServices.
The transport reads nodesExt[].services.n1ql, or n1qlSSL when TLS is on, and
that is the port every subsequent SQL++ statement goes to. If no node advertises
an n1ql entry at all, it falls back to 8093, or 18093 under TLS.
Two details in that discovery matter more than the lookup itself.
It is cached as a promise, not as a value. Concurrent first queries share one round trip rather than each issuing their own.
A failed discovery is not cached. One unreachable moment during startup would otherwise poison every later query on that connection.
Connect itself is one call before any of this: GET /pools/default, the cheapest
request that proves reachability and credentials together. Disconnect clears the
cached discovery; there are no sockets to close, because each statement is one
stateless HTTP request.
The same rule is why a pasted connection string’s port is thrown away. A
couchbase:// URL copied out of an application config carries the KV port, not
the management port. Storing it would point management traffic at 11210. The
scheme is read - it sets the SSL mode, require for couchbases:// and disable
for couchbase:// - and the port is not.
External addresses, NAT and port mapping
Discovery would be a mild convenience if a node only ever advertised the address
you already reached it on. A node can publish alternateAddresses.external
alongside its internal one, and the transport prefers it. That single preference
is what makes three common deployments work with no extra field:
- Docker port mapping. The node knows itself by a container-internal address; you reach it on a mapped host port. The external alternate address is the one routable from where the request originates.
- NAT. Same shape, at a different layer.
- Capella. The managed endpoint you are given is not the node’s internal
address either. Capella hosts without an explicit port are resolved through the
SRV record
_couchbases._tcp.<host>first; a DNS failure or an empty answer falls back to treating the host as a plain A record, which is what every self-hosted cluster needs anyway.
The local fixture shows how little has to be open. The repository’s
database-compose.yml runs couchbase:community-8.0.2 and needs 8091 for
management REST and 8093 for the query service, because the provider speaks HTTP
and never the binary KV protocol on 11210. Reproducing it by hand is documented:
docker run --rm -d --name cb -p 8091-8096:8091-8096 couchbase:community
# Community Edition rejects the Magma storage backend that couchbase-cli
# defaults to, and a single node cannot satisfy a replica.
docker exec cb couchbase-cli cluster-init -c 127.0.0.1 \
--cluster-username Administrator --cluster-password password123 \
--services data,index,query --cluster-ramsize 512 --cluster-index-ramsize 256
docker exec cb couchbase-cli bucket-create -c 127.0.0.1 \
-u Administrator -p password123 --bucket travel \
--bucket-type couchbase --bucket-ramsize 256 \
--storage-backend couchstore --bucket-replica 0
Then point a connection at 127.0.0.1:8091 with bucket travel. The
get started guide covers the container side in general;
--storage-backend couchstore and --bucket-replica 0 are the Couchbase-specific
part, and both are required.
What one connection covers, and what it does not
One bucket per connection. Multi-bucket browsing from a single connection is out of scope, and every schema and monitoring read is scoped to that bucket. Two buckets means two connections. There is no bucket switcher, and adding one would mean a schema tree whose contents no longer match what the monitoring panels are measuring.
Within the bucket, opening a collection generates a statement that projects the
key explicitly, because SELECT * FROM hotel nests the document under the
keyspace name and omits the key entirely:
SELECT META(d).id AS __id, d.* FROM `travel`.`inventory`.`hotel` AS d LIMIT 50;
Every statement is sent with scan_consistency: "request_plus", so you see your
own writes. The measurement behind that default, taken against Couchbase Server
8.0.2: immediately after an INSERT, a SELECT returned zero rows while
COUNT(*) already returned three, and the same SELECT returned three rows
seconds later. The trade is stated - request_plus waits for the index to catch
up, which costs latency on a write-heavy cluster - and callers opt out per
statement with { scanConsistency: 'not_bounded' }.
Two more boundaries belong on the same page as the connection form. Inline row
editing is not offered: the obstacle is not UPDATE, which SQL++ has, but the key
projection alias __id, which the shared editor’s primary-key heuristic would
turn into WHERE __id = '<key>' - a predicate no document satisfies, so the edit
would match zero documents and still report success. And Agent AUTO mode ends
engine-unsupported here, because the read-only profile it runs under is
database-native and only PostgreSQL, SQLite and DuckDB implement it. Agent PLAN
mode opens on this connection like any other: it runs no statement of yours,
writes nothing, and hands every statement it drafts to you to run. On Couchbase
its grounding infers field names from a sample of your own documents rather than
reading a catalog, because there is no catalog to read.