Container Image Management
The Control Plane manages the container images used for Postgres instances through a version manifest - a JSON document that maps each supported Postgres version and Spock major version to a specific, tested image tag.
When you create a database without specifying an image, the Control Plane looks up the default image for the requested Postgres and Spock version combination from the manifest and records the selection in the database's persisted state. The recorded image is used for all subsequent reconciliations until you explicitly apply an upgrade or provide an image override.
The Version Manifest
The version manifest is the source of truth for which container images the
Control Plane uses. The manifest is embedded in the Control Plane binary as a
fallback, fetched from downloads.pgedge.com at startup, and cached on disk so
the Control Plane remains operational when the manifest URL is temporarily
unreachable. The resolution order for the default and custom URL modes is:
- Remote URL (default:
downloads.pgedge.com, configurable viadocker_swarm.manifest_url) - Disk cache (populated from a previous successful remote fetch)
- Embedded binary (shipped with each Control Plane release)
For air-gapped deployments, docker_swarm.manifest_path provides a fourth
mode: the Control Plane reads a local file and skips all remote fetching.
See the Configuration Reference and
Installing the Control Plane for details.
The Control Plane logs a warning when falling back to the cache or embedded
manifest. With the default manifest URL, startup always succeeds because the
embedded binary manifest serves as the final fallback. With a custom
manifest_url, the embedded fallback is disabled: if the URL and disk cache
are both unavailable at startup, the Control Plane returns an error.
Note
The manifest is refreshed from the remote URL in the background approximately once per hour. A Control Plane restart is not required for routine manifest updates.
Image Tag Format
pgEdge image tags follow a structured naming convention that encodes the Postgres version, Spock version, image variant, and optional build number. The format is:
{pg_version}-spock{spock_version}-{variant}[-{build}]
For example: 17.9-spock5.0.6-standard-2
The following table describes each component of the image tag format:
| Component | Description | Examples |
|---|---|---|
pg_version |
Postgres major.minor version |
17.9, 18.4 |
spock_version |
Full Spock semver | 5.0.6, 5.0.9 |
variant |
Image variant | standard |
build |
Optional build number | 1, 2 |
Checking Available Images
This section explains how to query the Control Plane for available Postgres versions and pending image upgrades.
Supported Postgres Versions
To see which Postgres versions the Control Plane can use on each host, query
the /v1/hosts endpoint:
curl http://host-3:3000/v1/hosts
The response includes a supported_pgedge_versions field for each host that
lists the Postgres and Spock version combinations available on that host.
Available Image Upgrades
To check for available upgrades you need a running database created at a
lower image version than the current manifest default. If you created the
database without specifying an image, the manifest selected the latest image
and available_upgrades will be empty. Create the database with an older
postgres_version (for example 17.9 when 17.10 is available in the
manifest) so that an upgrade is visible. See
Creating a Database for instructions.
To check whether a newer stable image exists in the same Postgres major and
Spock major version bucket as your running database, include
available_upgrades in the response:
curl 'http://host-3:3000/v1/databases/example?include=available_upgrades'
The available_upgrades field in the response lists each candidate image:
{
"available_upgrades": [
{
"image": "ghcr.io/pgedge/pgedge-postgres:17.10-spock5.0.9-standard-1",
"postgres_version": "17.10",
"spock_version": "5"
}
]
}
An upgrade is listed when a stable manifest entry exists in the same
(postgres_major, spock_major) bucket and has a strictly higher Postgres
version than the database's current image.
For instructions on applying an image upgrade, see Image Upgrades.
Using a Custom Image
You can override the manifest-selected image by setting
orchestrator_opts.swarm.image in your create or update request. This
override is useful for:
- pinning to a specific patch level or build, such as a particular
standard-Nbuild or a Spock patch version not yet in the manifest. - testing pre-release images by deploying a dev build that is not in the manifest.
- air-gapped deployments that reference an image from a private registry.
Per-Database Override
Set image at the top-level orchestrator_opts.swarm to apply the same image
to all nodes. The following request creates a database pinned to a specific
image:
curl -X POST http://host-3:3000/v1/databases \
-H 'Content-Type:application/json' \
--data '{
"id": "example",
"spec": {
"database_name": "example",
"database_users": [
{
"username": "admin",
"db_owner": true,
"attributes": ["SUPERUSER", "LOGIN"]
}
],
"postgres_version": "17.9",
"spock_version": "5",
"orchestrator_opts": {
"swarm": {
"image": "ghcr.io/pgedge/pgedge-postgres:17.9-spock5.0.6-standard-2"
}
},
"nodes": [
{ "name": "n1", "host_ids": ["host-1"] },
{ "name": "n2", "host_ids": ["host-2"] }
]
}
}'
Per-Node Override
Set image on a specific node's orchestrator_opts.swarm to use a different
image on that node only. The node-level value takes precedence over the
database-level value. The following request pins node n1 to a specific image
while n2 uses the manifest default:
curl -X POST http://host-3:3000/v1/databases \
-H 'Content-Type:application/json' \
--data '{
"id": "example",
"spec": {
"database_name": "example",
"database_users": [
{
"username": "admin",
"db_owner": true,
"attributes": ["SUPERUSER", "LOGIN"]
}
],
"postgres_version": "17.9",
"spock_version": "5",
"nodes": [
{
"name": "n1",
"host_ids": ["host-1"],
"orchestrator_opts": {
"swarm": {
"image": "ghcr.io/pgedge/pgedge-postgres:17.9-spock5.0.6-standard-2"
}
}
},
{ "name": "n2", "host_ids": ["host-2"] }
]
}
}'
Digest-Pinned Images
You can append a digest to an image reference for byte-exact pinning. The Control Plane strips the digest before version parsing and checks accessibility against the digest-qualified reference.
To obtain the digest for a tag, run the following command on any host that has pulled the image:
docker inspect ghcr.io/pgedge/pgedge-postgres:17.10-spock5.0.9-standard-1 \
--format='{{index .RepoDigests 0}}'
The output is the fully qualified image:tag@sha256:<digest> string. Use that
value in orchestrator_opts.swarm.image when creating the database:
curl -X POST http://host-3:3000/v1/databases \
-H 'Content-Type:application/json' \
--data '{
"id": "example",
"spec": {
"database_name": "example",
"database_users": [
{
"username": "admin",
"db_owner": true,
"attributes": ["SUPERUSER", "LOGIN"]
}
],
"postgres_version": "17.10",
"spock_version": "5",
"orchestrator_opts": {
"swarm": {
"image": "ghcr.io/pgedge/pgedge-postgres:17.10-spock5.0.9-standard-1@sha256:<digest>"
}
},
"nodes": [
{ "name": "n1", "host_ids": ["host-1"] },
{ "name": "n2", "host_ids": ["host-2"] }
]
}
}'
Tip
Digest pinning guarantees that you run a specific immutable image even if the tag is later reassigned to a different image in the registry.
Image Validation
When orchestrator_opts.swarm.image is set, the Control Plane validates the
image on every host before accepting the request. Validation runs in parallel
across all hosts and returns a 400 Bad Request if any validation fails.
The Control Plane checks the following conditions:
- registry accessibility: the image reference is reachable in the registry. The check uses a lightweight manifest fetch; no image layers are downloaded.
- Postgres version prefix match: if the tag follows the pgEdge format
(
{pg_version}-spock{spock_version}-{variant}), thepg_versionin the tag must start with thepostgres_versionin the database spec. - Spock version prefix match: the
spock_versionin the tag must start with all components ofspock_versionin the spec. For example, a spec declaring"spock_version": "5"accepts tag versions5,5.0.6, or5.0.9.
Tags that do not follow the pgEdge format (for example, a custom dev tag with no version structure) skip the version match checks - only registry accessibility is verified.
Validation Error Examples
The following examples show the error responses returned for common validation failures.
The following request triggers a Postgres version mismatch error because the
spec requests 17.9 but the tag contains 17.10. The image must exist in
the registry for version validation to run - the registry check runs first
and short-circuits if the image is not found:
curl -X POST http://host-3:3000/v1/databases \
-H 'Content-Type:application/json' \
--data '{
"id": "example",
"spec": {
"database_name": "example",
"database_users": [
{"username": "admin", "db_owner": true, "attributes": ["SUPERUSER", "LOGIN"]}
],
"postgres_version": "17.9",
"spock_version": "5",
"orchestrator_opts": {
"swarm": {
"image": "ghcr.io/pgedge/pgedge-postgres:17.10-spock5.0.9-standard-1"
}
},
"nodes": [{"name": "n1", "host_ids": ["host-1"]}]
}
}'
The Control Plane returns a 400 Bad Request with the following body:
{
"message": "validation error for node n1, host host-1: image tag postgres version 17.10 does not match spec version 17.9",
"name": "invalid_input"
}
The following request triggers an image-not-found error because the tag does not exist in the registry:
curl -X POST http://host-3:3000/v1/databases \
-H 'Content-Type:application/json' \
--data '{
"id": "example",
"spec": {
"database_name": "example",
"database_users": [
{"username": "admin", "db_owner": true, "attributes": ["SUPERUSER", "LOGIN"]}
],
"postgres_version": "17.9",
"spock_version": "5",
"orchestrator_opts": {
"swarm": {
"image": "ghcr.io/pgedge/pgedge-postgres:17.9-spock4.0.0-standard-1"
}
},
"nodes": [{"name": "n1", "host_ids": ["host-1"]}]
}
}'
The Control Plane returns a 400 Bad Request with the following body:
{
"message": "validation error for node n1, host host-1: image \"ghcr.io/pgedge/pgedge-postgres:17.9-spock4.0.0-standard-1\" could not be verified: image \"ghcr.io/pgedge/pgedge-postgres:17.9-spock4.0.0-standard-1\" not found or inaccessible: Error response from daemon: manifest unknown",
"name": "invalid_input"
}
When the registry is unreachable due to a network error, the Control Plane returns the following error:
{
"message": "validation error for node n1, host host-1: image \"ghcr.io/pgedge/pgedge-postgres:17.9-spock5.0.6-standard-2\" could not be verified: context deadline exceeded",
"name": "invalid_input"
}
Warning
Registry reachability checks add latency to create and update requests. In
environments where the registry is unavailable from all hosts (such as a
fully air-gapped deployment), use manifest_path to load images from a
local manifest and ensure all referenced images are pre-pulled on each
host.
Image Persistence
Once an image is selected - either from the manifest or from an
orchestrator_opts.swarm.image override - the Control Plane records the
selection in the database's persisted state as the resolved_image. The
Control Plane uses this recorded value on every subsequent reconciliation.
A database's image does not change automatically across Control Plane upgrades.
The Control Plane updates the recorded resolved_image only when you:
- Apply an explicit image upgrade via
POST /v1/databases/{id}/upgrade(see Upgrading a Database). - Update the database spec with a different
postgres_version,spock_version, ororchestrator_opts.swarm.image.
Note
If you update a database image directly via docker service update outside
the Control Plane, the change will be overwritten on the next
reconciliation. The Control Plane is the authoritative source of truth for
Docker service configuration.
Next Steps
The following documents provide additional context for managing container images in the Control Plane:
- The Upgrading a Database document explains how to apply image upgrades and perform minor and major Postgres version upgrades.
- The Configuration Reference document
describes the
docker_swarm.manifest_urlanddocker_swarm.manifest_pathsettings used to customize manifest resolution. - The Creating a Database document explains how to create a database and specify image options at creation time.