Spilo: Patroni and PostgreSQL packed into one image, with a caveat about releases
Highly available elephant herd: HA PostgreSQL cluster using Docker
At a glance
- What is it?
- Spilo bundles PostgreSQL with Patroni into a single Docker image so that several instances can form a highly available cluster, with the members agreeing on a shared etcd. The unusual thing about the project is its release policy, which asks you to build the images from source yourself because the maintainers do not make regular releases or publish latest images, and it tells you to bring your own load balancer.
- Who is it for?
- Spilo fits a team that wants Patroni's failover model delivered as a container rather than as a set of scripts, and that is prepared to build the image itself and to run a load balancer in front of the cluster.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 7 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What the image actually is, and the etcd requirement underneath it
Spilo is a Docker image that provides PostgreSQL and Patroni bundled together. Patroni is a template for PostgreSQL HA, and multiple Spilos can create a resilient high availability PostgreSQL cluster.
The mechanism is that all participating Spilos need to start with identical etcd addresses and cluster names. That is the whole coordination contract, and it is worth being clear about because it is the part that makes the system a cluster rather than a set of containers. etcd holds the consensus state about which node is the leader, and the containers agree on where etcd is and what the cluster is called.
The name comes from Georgian. Spilo's name derives from the Georgian word for elephant, and the repository description calls it a highly available elephant herd. The metaphor works better than it might, since a herd implies members that are individually replaceable and coordinated through a shared understanding, which is roughly what Patroni does with the nodes in a Postgres cluster.
The primary language in the repository metadata is Python, which is the language Patroni is written in, and the licence is Apache 2.0 with the LICENSE file at the root. The topic tags are data-infrastructure, docker, docker-image, high-availability, patroni, postgresql and python.
Now the release story, because it is unusual and it changes how you should approach the project. The README asks users to build the Docker images themselves from source code using the latest tags, to benefit from ongoing improvements and fixes. The team continues to maintain the project and address issues, but does not make regular releases nor publishes latest Docker images.
The tags confirm the shape of that. The three listed releases are 3.0-p1 from 2023-03-10, 2.1-p9 from 2022-12-30 and 2.1-p7 from 2022-09-23, so the published release line stopped in March 2023, while the last push to the repository was on 2026-10-16 and the default branch is trigger rather than master. The repository is not archived, so the work is continuing, but you are expected to consume it by building rather than by pulling a tagged image.
There is an existing Helm chart that relies on Spilo and Patroni to provision a five-node PostgreSQL HA cluster in a Kubernetes and Google Compute Engine environment, and the chart deploys the Spilo Docker images and not just bare Patroni. That chart is in the incubator stage of the Kubernetes charts repository, which is itself a statement about maturity.
Building the image, and the arguments that control its contents
The build is two commands, from the postgres-appliance directory:
$ cd postgres-appliance
$ docker build --tag $YOUR_TAG .And the image is run locally with:
$ docker run -it your-spilo-image:$YOUR_TAGThe build arguments are where the interesting choices live, and they exist because one image serves several use cases. The comment says a few build arguments are defined in the Dockerfile and it is possible to change them by specifying --build-arg arguments.
WITH_PERL=false sets to true if you want to install perl and plperl packages into the image. The default is off, which keeps perl out of a database image that does not need it.
PGVERSION is the PostgreSQL version to build, with the example 12, and PGOLDVERSIONS lists the older versions to include, for example 9.5 9.6 10 11, so an image can carry several server versions side by side.
DEMO=false sets to true to build the smallest possible image which will work only on Kubernetes. The comment on that one is the sharpest: the minimal image is not a general purpose Postgres image, it is a Kubernetes-only one, and the trade is a much smaller artefact in exchange for portability you do not get.
TIMESCALEDB_APACHE_ONLY=true sets to false to build the timescaledb community version, under the Timescale License, and TIMESCALEDB_TOOLKIT=true sets to false to skip installing the toolkit with the timescaledb community edition, which is only relevant when TIMESCALEDB_APACHE_ONLY is false. So the default builds the Apache licensed build of TimescaleDB, and the two flags are there for the people who need the community edition and accept its licence terms.
ADDITIONAL_LOCALES takes a space separated list of additional UTF-8 locales to build into the image, with the example de_DE pl_PL fr_FR. Since the image ships the locales it was built with, this is a build-time decision rather than a runtime one, which means adding a locale means a rebuild.
Once the container is running, you can look inside it with:
$ docker exec -it $CONTAINER_NAME bashThe repository layout explains the two build targets. There is a postgres-appliance/ directory and an etcd-cluster-appliance/ directory, so the same project builds both the database image and the etcd image, which is convenient when you want a self-contained cluster to test against rather than running etcd separately. There is also a kubernetes/ directory for the manifests, a contrib/ directory, a docs/ directory, and a mkdocs.yml for the documentation build, along with a tox.ini for the Python tooling and a delivery.yaml.
The load balancer requirement, which is not optional
There is one piece of Spilo's setup that is a precondition rather than a detail, and the README marks it as important.
Spilo's setup assumes that you have correctly configured a load balancer, such as HAProxy, ELB or a Google load balancer, that directs client connections to the master. The README then gives two ways to achieve this, and the choice depends on what your load balancer can do.
Option A is for a load balancer that relies on the status code to distinguish between the healthy and failed nodes, like ELB, in which case you need to configure it to poll the API URL. The Patroni API is what reports the current leader, so a balancer that can only make health decisions from status codes has to be pointed at that API rather than at a database connection.
Option B is for everything else, where you can use callback scripts to change the load balancer configuration dynamically. That is the approach where Patroni, or something watching it, updates the balancer's target list as the leader changes.
The reason this matters is that Patroni's model is a single writable master with read replicas, and the failover is only complete when client traffic follows the new leader. A Patroni cluster without a balancer that tracks the leader is a cluster where a failover completes on the server side and the client side keeps talking to the old primary. Spilo does not solve that for you, and the documentation is explicit that the load balancer is assumed to be already correct.
So the practical deployment order is: stand up etcd, because the members need it to agree on the leader, put a load balancer in front that can see the Patroni API or accept callbacks, and only then start the Spilo containers with identical etcd addresses and cluster names. Getting that order wrong is the usual reason a first attempt looks like a cluster that does not fail over.
Connecting to it, and the two user accounts you get for free
The connection section is short, and it distinguishes administrative access from application access, which is the split that matters for anything you build on top.
PostgreSQL is configured by default to listen to port 5432. Spilo master initializes PostgreSQL and creates the superuser and replication user, postgres and standby by default. You will need to set up Spilo to create a database and roles for your application.
Administrative connections use psql with the host, port, user and database:
psql -h myfirstspilo.example.com -p 5432 -U admin -d postgresApplication connections, once you have created a database and roles, are like any other PostgreSQL cluster:
psql -h myfirstspilo.example.com -p 5432 -U wow_app -d wow
psql -d "postgresql://myfirstspilo.example.com:5432/wow?user=wow_app"Two accounts is a small detail with a large consequence. The superuser and the replication user are created for you, and the replication user in particular is a member of the cluster's own machinery rather than something you chose. Your application should get its own role, which is what the second block of commands shows with wow_app and a database named wow.
The two connection forms in that block are worth comparing. The first is the traditional host, port, user, database form, and the second is the URI form, which is what you would put in a connection string in an application. Both point at the same place, but the URI form is the one that ends up in configuration files and environment variables, and it is the one where a mistake is easy to make because the whole thing is a single string.
The port is the other thing to note. It is 5432, the PostgreSQL default, and it is what the load balancer exposes to clients. The individual Spilo containers are behind that balancer, so the port is a property of your load balancer configuration rather than of the containers themselves, and the containers do not all need to be reachable from outside for the scheme to work.
Configuration by environment variable, and the roadmap that explains the project
Everything Spilo does is configured through environment variables, and the values are supplied in one of two ways depending on how you run it.
They are either supplied manually via the environment, whenever Spilo is launched as a set of Docker containers, or added in the configuration file or manifest, whenever Spilo is used in a Docker orchestration environment such as Kubernetes or Docker Compose.
The full list of environment variables lives in a separate document, ENVIRONMENT.rst, and the README links to it rather than duplicating it. For local testing, one variable is passed on the command line:
docker run -it -e YOUR_ENV_VAR=test your-spilo-image:latestThe environment variable approach is the right one for this kind of system, and the reason is that the same image has to behave differently in different contexts. In a plain docker run, the operator supplies the etcd addresses, the cluster name, the authentication settings and the load balancer integration by hand. In Kubernetes, the same values arrive from a ConfigMap or a manifest, and the difference between the two is only where the strings come from.
The roadmap section is what explains why the project looks the way it does. Spilo is currently evolving, and its creators are working on a Postgres operator that would make it simpler to deploy scalable Postgres clusters in a Kubernetes environment, and also to do maintenance tasks. Spilo would serve as an essential building block for this.
That roadmap statement is a design decision stated openly, and it has an obvious implication for how you should evaluate the project. Spilo is the image layer of a stack whose intended top layer is an operator, and the Kubernetes chart in the incubator directory is the transitional shape of that. If your context is Kubernetes, the operator is the destination and Spilo is the substrate, and the older chart that deploys the Spilo images directly is the thing being replaced. If your context is a plain set of containers or Docker Compose, then Spilo is the product, and the load balancer is the part you have to get right.
One more thing the README asks for is worth repeating because it is the most common friction in a project like this. It encourages users to build the Docker images themselves from source code using the latest tags, to benefit from ongoing improvements and fixes, because the team continues to maintain the project and address issues but does not make regular releases nor publish latest Docker images.
That instruction is unusual, and it is a real operational consequence. A team that wants a stable, pinned, pullable image has to build it and host it somewhere themselves, which means they own the rebuild and the version pinning rather than receiving them. In exchange they get the current fixes, which on a project whose release line stopped in 2023 is the difference between running a current PostgreSQL and running an old one.
Editorial conclusion
Spilo fits a team that wants Patroni's failover model delivered as a container rather than as a set of scripts, and that is prepared to build the image itself and to run a load balancer in front of the cluster. It is the wrong tool if you need a published image you can pull without a build step, since the project states that it does not make regular releases or publish latest Docker images, and it is the wrong choice if you are on Kubernetes, where the same job is done by an operator rather than by a herd of containers. Before deploying, configure the load balancer to route to the master, decide how your members will reach etcd, and read the ENVIRONMENT.rst file, since the whole configuration surface is environment variables.
Frequently asked questions
What is Spilo and what does it combine?
Spilo is a Docker image that provides PostgreSQL and Patroni bundled together, and multiple Spilos can form a resilient high availability PostgreSQL cluster. All participating Spilos must start with identical etcd addresses and cluster names, since that is how the members agree on who the leader is.
How do I build the Spilo Docker image?
Change into the postgres-appliance directory and run docker build --tag $YOUR_TAG ., then run it with docker run -it your-spilo-image:$YOUR_TAG. Build arguments let you change the PostgreSQL version, add perl, build a minimal Kubernetes-only image, switch the TimescaleDB edition, and add extra locales.
What does Spilo require before it will work as a cluster?
A correctly configured load balancer, such as HAProxy, ELB or a Google load balancer, that directs client connections to the master. Either configure the balancer to poll the Patroni API URL if it makes health decisions from status codes, or use callback scripts to change the balancer configuration dynamically.
Does Spilo publish Docker images?
Not regular ones. The README says the team continues to maintain the project and address issues but does not make regular releases nor publishes latest Docker images, and it encourages users to build the images themselves from source using the latest tags. Images for PostgreSQL 14 are available in the GitHub container registry as linux/amd64 and linux/arm64.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/zalando-spilo)Was this useful?