edoburu/docker-pgbouncer turns pgbouncer.ini into environment variables
Minimal PgBouncer image that is easy to configure
At a glance
- What is it?
- This image is a minimal PgBouncer on Alpine Linux that generates its own pgbouncer.ini and userlist.txt from environment variables at startup, so a connection pooler can be brought up with a single docker run and no mounted configuration. It is built for deployments where the pooler is configured by the same mechanism as the rest of the container, and it leaves the awkward settings, like ports and pid files, to the platform.
- Who is it for?
- edoburu/docker-pgbouncer fits a container platform where configuration is delivered as environment variables, where the pooler should be a disposable sidecar rather than a hand-configured host service, and where a small image and a standard port matter. It is the wrong tool if you need the settings the image deliberately leaves out, such as custom ports, syslog or pid file handling, or if you need a configuration format the environment variable mapping does not cover.
- Can I use it commercially?
- Yes. MIT 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 5 days ago.
- What is it written in?
- Mainly Shell, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 24, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A 15MB image that writes its own configuration
This is a minimal PgBouncer image based on Alpine Linux, and the feature list is short enough to quote in full. It is very small and quick to pull, at just 15MB. It is configurable using environment variables. It uses the standard Postgres port 5432, to work transparently for applications. It includes PostgreSQL client tools such as psql and pg_isready. Authentication is MD5 by default. And /etc/pgbouncer/pgbouncer.ini and /etc/pgbouncer/userlist.txt are auto-created if they don't exist.
That last point is the design decision. A conventional PgBouncer deployment means writing an ini file, writing a userlist, and mounting both. This image inverts that: you describe the database with environment variables and the startup script writes the files for you. That is why the image is described as easy to configure rather than merely small.
The purpose of the pooler itself is explained in the README with numbers attached. PostgreSQL connections take up a lot of memory, about 10MB per connection according to the linked post, and there is a significant startup cost to establish a connection with TLS, hence web applications gain performance by using persistent connections.
The mechanism is placement. By putting PgBouncer between the web application and the actual PostgreSQL database, the memory and startup costs are reduced: the application keeps persistent connections to PgBouncer, while PgBouncer keeps only a few connections to the PostgreSQL server and reuses the same connection for multiple clients.
For an engineer running a containerised application, that is a familiar shape, and it explains why this image exists rather than a hand-installed PgBouncer.
Two ways to hand the database credentials over
The documented run command passes everything through one variable:
docker run --rm \
-e DATABASE_URL="postgres://user:pass@postgres-host/database" \
-p 5432:5432 \
edoburu/pgbouncerOr the same information split into separate variables:
docker run --rm \
-e DB_USER=user \
-e DB_PASSWORD=pass \
-e DB_HOST=postgres-host \
-e DB_NAME=database \
-p 5432:5432 \
edoburu/pgbouncerDuring startup the image generates the necessary userlist.txt and pgbouncer.ini entries to match this information, and connecting is expected to work, which you can check with the client tools bundled in the image:
psql 'postgresql://user:pass@localhost/dbname'For a multi-user setup, the README notes that you can use DATABASE_URLS instead of DATABASE_URL with a comma separated list of URLs. That is the form you want when several users, each with their own credentials, connect through one pooler.
One detail to be aware of is that you can also connect with a single user to PgBouncer, and from there retrieve the actual database password, by setting AUTH_USER. The README points at a Cybertec article for the example of that pattern.
Almost every ini setting gets an environment variable
The configuration section states the general rule. Almost all settings found in pgbouncer.ini can be defined as environment variables, except a few that make little sense in a Docker environment, like port numbers, syslog and pid settings. The entrypoint script is linked for details.
The example shows the shape of the mapping:
docker run --rm \
-e DATABASE_URL="postgres://user:pass@postgres-host/database" \
-e POOL_MODE=session \
-e SERVER_RESET_QUERY="DISCARD ALL" \
-e MAX_CLIENT_CONN=100 \
-p 5432:5432
edoburu/pgbouncerPOOL_MODE, SERVER_RESET_QUERY and MAX_CLIENT_CONN are ordinary PgBouncer settings, spelled in the same uppercase form, and the value of a setting that takes a string is quoted the same way you would quote it in an ini file.
The excluded settings are the interesting part of the design. A container gets its network identity from the platform, so the port is not something the pooler should configure for itself. Syslog is a host facility, and a pid file is meaningless in a container that is started, stopped and replaced. Leaving them out is not an oversight, it is the reason the image can claim to work transparently with applications expecting port 5432.
The practical consequence for an operator is that you cannot configure everything this way, and the README is explicit about which things you cannot. If your deployment needs one of those, you need a mounted configuration file, which is the next step.
Mounting your own pgbouncer.ini and userlist.txt
When the default pgbouncer.ini is not sufficient, or when you want multiple users to connect through a single instance, the README tells you to mount an updated configuration:
docker run --rm \
-e DB_USER=user \
-e DB_PASSWORD=pass \
-e DB_HOST=postgres-host \
-e DB_NAME=database \
-v pgbouncer.ini:/etc/pgbouncer/pgbouncer.ini:ro
-p 5432:5432
edoburu/pgbouncerOr extend the Dockerfile:
FROM edoburu/pgbouncer:1.11.0
COPY pgbouncer.ini userlist.txt /etc/pgbouncer/The behaviour when a file already exists is worth knowing precisely. When the pgbouncer.ini file exists, the startup script will not override it. An extra entry will be written to userlist.txt when DATABASE_URL contains credentials, or when DB_USER and DB_PASSWORD are defined. So the configuration file wins over the generated one, while the credential list is still augmented rather than replaced.
The userlist.txt format is either a plaintext password:
"username" "plaintext-password"or an MD5 value:
"username" "md5<md5 of password + username>"The repository ships a helper for generating that file, examples/generate-userlist, and the README shows it being appended to the list:
examples/generate-userlist >> userlist.txtPostgreSQL has to accept the pooler host first
There is one prerequisite outside the container that trips people up, and the README gives it its own section.
PostgreSQL has to accept connections from the machine where PgBouncer runs. That means updating listen_addresses in postgresql.conf and accepting incoming connections from your IP range in pg_hba.conf, for example a range like 10.0.0.0/8:
# TYPE DATABASE USER ADDRESS METHOD
host all all 10.0.0.0/8 md5The reason is obvious once you draw the topology. The pooler is a separate host or container with its own address, and it is the pooler, not your application, that opens the connections to PostgreSQL. If PostgreSQL only accepts localhost, the pooler cannot connect to it at all, and the container will start and still serve nobody.
The MD5 method in that example line also lines up with the image default of MD5 authentication, so the two ends agree without further configuration.
This is the kind of step that appears in no container image, because it belongs to the database side, and it is the first thing to check when a freshly started pooler refuses connections that should work.
The admin console needs an admin user with a password
PgBouncer exposes a special pgbouncer database, and reaching it from this image needs one extra piece of setup.
When an admin user is defined, and it has a password in the userlist.txt, it can connect to that database:
psql postgres://postgres@hostname-of-container/pgbouncer # outside container
psql postgres://127.0.0.1/pgbouncer # inside containerThat requires a custom configuration, or a mount of a custom userlist.txt in the docker file. Which is the point worth internalising: the admin console is not available out of the box, because the default generated userlist has no admin user with a password.
Once connected, the console accepts the usual commands:
SHOW STATS;
SHOW SERVERS;
SHOW CLIENTS;
SHOW POOLS;and it allows temporary disconnecting the backend database, for example for restarts, while the web applications keep their connection to PgBouncer:
PAUSE;
RESUME;That last pair is the operationally interesting one. Pausing lets you take the database server down for a restart without dropping the application connections on the other side of the pooler, which is the behaviour that makes a pooler worth having in the first place.
How the image is built, and how it is released
The Dockerfile shows the build strategy, and it is a source build rather than a package install. The build stage starts from alpine:3.24, takes a VERSION argument defaulting to 1.26.1, installs the build toolchain including autoconf, automake, gcc, libevent-dev, openssl-dev and pkgconfig, downloads the PgBouncer release tarball from the upstream downloads directory, configures with a prefix of /usr, builds with make -j and strips the binary.
The final stage is also alpine:3.24, with libevent and postgresql-client added, the directories /etc/pgbouncer, /var/log/pgbouncer and /var/run/pgbouncer created, an empty userlist.txt touched, and the log and config directories owned by the postgres user. The entrypoint script and the built binary are copied in, the upstream example pgbouncer.ini and userlist.txt are copied in with an .example suffix, port 5432 is exposed, the container runs as the postgres user, and the command line is /usr/bin/pgbouncer with /etc/pgbouncer/pgbouncer.ini as its argument.
The Makefile handles the image build with buildx, with separate targets for amd64 and arm64 and a push target that builds both platforms at once, so the published image is multi-arch.
The releases carry a suffix, v1.26.0-p0 on 2026-09-30, v1.25.2-p0 on 2026-06-10 and v1.25.1-p0 on 2025-12-20, and the last push was on 2026-10-19. The repository is not archived and the licence is MIT.
Deployment examples for Kubernetes and Docker Compose live in the examples/kubernetes and examples/docker-compose directories, and the topics list both kubernetes and postgresql, which is where the image is meant to run.
Editorial conclusion
edoburu/docker-pgbouncer fits a container platform where configuration is delivered as environment variables, where the pooler should be a disposable sidecar rather than a hand-configured host service, and where a small image and a standard port matter. It is the wrong tool if you need the settings the image deliberately leaves out, such as custom ports, syslog or pid file handling, or if you need a configuration format the environment variable mapping does not cover. Verify first that every setting your pgbouncer.ini needs can be expressed as an environment variable, then confirm that PostgreSQL accepts connections from the address the pooler runs on, since that host rule has to be in place before the pooler can be reached at all.
Frequently asked questions
How do I start edoburu/docker-pgbouncer with my database credentials?
Pass a DATABASE_URL environment variable, or the separate DB_USER, DB_PASSWORD, DB_HOST and DB_NAME variables, and publish port 5432. The entrypoint generates the pgbouncer.ini and userlist.txt entries to match during startup.
Can I configure every PgBouncer setting with environment variables?
Almost all pgbouncer.ini settings can be set as environment variables, including POOL_MODE, SERVER_RESET_QUERY and MAX_CLIENT_CONN. The exceptions are the ones that make little sense in a Docker environment, such as port numbers, syslog and pid settings, and those need a mounted configuration file.
How do I connect to the PgBouncer admin console from this image?
Define an admin user with a password in the userlist.txt, which requires a custom configuration or a mounted userlist.txt, then connect to the pgbouncer database. From there you can run SHOW STATS, SHOW SERVERS, SHOW CLIENTS, SHOW POOLS, and PAUSE and RESUME to detach the backend briefly.
Why can my container reach the pooler but not the database?
PostgreSQL must accept connections from the machine where PgBouncer runs, so listen_addresses in postgresql.conf and pg_hba.conf need to cover that address range. The pooler opens the connections to PostgreSQL, not your application, so a PostgreSQL that only accepts localhost leaves the pooler unable to connect.
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/edoburu-docker-pgbouncer)Was this useful?