# Docker environment reference

> POSTGRES_PASSWORD, POSTGRES_USER, POSTGRES_DB, POSTGRES_INITDB_ARGS, POSTGRES_HOST_AUTH_METHOD, PGDATA, init scripts under /docker-entrypoint-initdb.d, and injected server opts.

- Repository: malisper/pgrust
- GitHub: https://github.com/malisper/pgrust
- Human docs: https://grok-wiki.com/public/docs/malisper-pgrust-7e0c8e82bc21
- Complete Markdown: https://grok-wiki.com/public/docs/malisper-pgrust-7e0c8e82bc21/llms-full.txt

## Source Files

- `Dockerfile`
- `docker/entrypoint.sh`
- `docker/README.md`
- `README.md`
- `.dockerignore`

---

---
title: "Docker environment reference"
description: "POSTGRES_PASSWORD, POSTGRES_USER, POSTGRES_DB, POSTGRES_INITDB_ARGS, POSTGRES_HOST_AUTH_METHOD, PGDATA, init scripts under /docker-entrypoint-initdb.d, and injected server opts."
---

The root `Dockerfile` and `docker/entrypoint.sh` implement an official-postgres-shaped image contract: env vars for first-boot identity and auth, `PGDATA` volume layout, `/docker-entrypoint-initdb.d` scripts, `ENTRYPOINT ["docker-entrypoint.sh"]` / `CMD ["postgres"]`, gosu step-down to uid 999, `EXPOSE 5432`, and `STOPSIGNAL SIGINT`. The server and catalog bootstrap are both the pgrust binary at `/usr/local/bin/pgrust-postgres` (`--initdb` plus postmaster); the only C tool retained is PGDG PostgreSQL 18 `psql` for init SQL and healthchecks.

## Contract surface

| Surface | Value / behavior |
| --- | --- |
| Image entrypoint | `docker-entrypoint.sh` (also linked as `/docker-entrypoint.sh`) |
| Default command | `postgres` (rewritten to `pgrust-postgres`) |
| Server + initdb binary | `/usr/local/bin/pgrust-postgres` (`PGRUST_BIN`) |
| Client | `psql` on `PATH` under `/usr/lib/postgresql/18/bin` |
| Share tree | `PG_SHAREDIR` default `/usr/share/postgresql/18` (passed to `--initdb` via `-L`) |
| Data volume | `VOLUME /var/lib/postgresql/data`, `ENV PGDATA=/var/lib/postgresql/data` |
| Socket dir | `/var/run/postgresql` (owned for `postgres`, mode 3777 at image build) |
| User | `postgres` uid/gid 999 |
| Port | 5432 |
| Locale | `LANG=en_US.utf8` |
| Stop | `SIGINT` (fast shutdown; temp init server also stopped with SIGINT) |

```bash
docker build -t pgrust .
docker run --rm -e POSTGRES_PASSWORD=secret -p 5432:5432 pgrust
```

Published image example: `malisper/pgrust:v0.1` with the same env contract.

## Environment variables

`docker_setup_env` loads the official-style `POSTGRES_*` vars (with `_FILE` secret support) before any init or server start when argv is `postgres` (or flags that become `postgres …`).

### `file_env` and Docker secrets

`file_env VAR [DEFAULT]` supports either `VAR` or `VAR_FILE` (path whose contents become the value). Setting both exits with:

```text
error: both VAR and VAR_FILE are set (but are exclusive)
```

Applied to: `POSTGRES_PASSWORD`, `POSTGRES_USER`, `POSTGRES_DB`, `POSTGRES_INITDB_ARGS`.

### Primary variables

<ParamField body="POSTGRES_PASSWORD" type="string" required>
Superuser password. Required on first boot unless `POSTGRES_HOST_AUTH_METHOD=trust`. Empty password + non-trust auth exits with *Database is uninitialized and superuser password is not specified.* After initdb, set via `ALTER ROLE CURRENT_USER WITH PASSWORD …` on a temporary server (`docker_setup_password`) because pgrust `--initdb` has no `--pwfile`. Also used as default `PGPASSWORD` during init-script SQL if unset.
</ParamField>

<ParamField body="POSTGRES_USER" type="string" default="postgres">
Bootstrap superuser name. Passed to `--initdb` as `--username`. Used by `psql` for init SQL (`--username`).
</ParamField>

<ParamField body="POSTGRES_DB" type="string" default="same as POSTGRES_USER">
Application database name. Created after the temp server starts if missing (`CREATE DATABASE`). Init SQL runs against this db when set; catalog checks for existence use the `postgres` database with `POSTGRES_DB` temporarily cleared.
</ParamField>

<ParamField body="POSTGRES_INITDB_ARGS" type="string" default="empty">
Extra argv spliced into the `--initdb` invocation via shell `eval` (space-separated options only). Always preceded by fixed flags: `-D "$PGDATA" -L "$PG_SHAREDIR" --username "$POSTGRES_USER"`.
</ParamField>

<ParamField body="POSTGRES_HOST_AUTH_METHOD" type="string" default="trust (at pg_hba write time)">
Appended to `$PGDATA/pg_hba.conf` as `host all all all <method>`. Empty until `pg_setup_hba_conf`, which defaults to `trust` for TCP because password host auth is not yet enforced by pgrust (see below). Explicit values always win; non-`trust`/`reject` methods emit a pgrust-specific WARNING.
</ParamField>

<ParamField body="PGDATA" type="string" default="/var/lib/postgresql/data">
Data directory. Created with mode `0700` when possible; ownership fixed to `postgres` when the entrypoint runs as root. First-boot detection: non-empty `$PGDATA/PG_VERSION` sets `DATABASE_ALREADY_EXISTS` and skips init.
</ParamField>

### Related variables

| Variable | Default | Role |
| --- | --- | --- |
| `POSTGRES_INITDB_WALDIR` | unset | If set, creates/chowns the WAL directory and passes `--waldir` to `--initdb` |
| `POSTGRES_PASSWORD_FILE` / `POSTGRES_USER_FILE` / … | unset | Docker secrets via `file_env` |
| `PGPASSWORD` | falls back to `POSTGRES_PASSWORD` during first-boot SQL | Export for `psql` if local HBA needs a password |
| `PGPORT` | `5432` | Temp server port (`-p`) during init |
| `PG_MAJOR` | `18` (image `ENV`) | Old-layout detection when `PGDATA` is `/var/lib/postgresql/$PG_MAJOR/docker` |
| `PGRUST_BIN` | `/usr/local/bin/pgrust-postgres` | Absolute path to server/initdb binary |
| `PG_SHAREDIR` | `${PGRUST_PGSHAREDIR:-/usr/share/postgresql/18}` | Share dir for `-L` and bootstrap templates |
| `PGRUST_PGSHAREDIR` | baked at image build (`/usr/share/postgresql/18`) | Tz tree path in the binary; also fallback for `PG_SHAREDIR` |
| `RUST_MIN_STACK` | `33554432` | Exported every entrypoint run |
| `LANG` | `en_US.utf8` | Image locale |
| `PATH` / `LD_LIBRARY_PATH` | PGDG bin + `/opt/runlibs` | `psql` and its non-glibc deps |

## First-boot lifecycle

Runs only when argv is `postgres` (not help/version) and `$PGDATA/PG_VERSION` is absent.

```text
root? ──gosu postgres──┐
                       ▼
docker_setup_env → docker_create_db_directories
                       ▼
docker_verify_minimum_env → docker_error_old_databases
                       ▼
docker_init_database_dir   # pgrust-postgres --initdb …
                       ▼
pg_setup_hba_conf          # append host line
                       ▼
docker_temp_server_start   # socket-only, listen_addresses=''
  docker_setup_password
  docker_setup_db
  docker_process_init_files /docker-entrypoint-initdb.d/*
docker_temp_server_stop
                       ▼
exec pgrust-postgres -D $PGDATA [PGRUST_SERVER_OPTS] -c listen_addresses='*' …
```

**Success signal (stdout):**

```text
PostgreSQL init process complete; ready for start up.
```

**Existing data dir (skip init):**

```text
PostgreSQL Database directory appears to contain a database; Skipping initialization
```

Then the same final `exec` of the pgrust server.

### Initdb invocation

```bash
"$PGRUST_BIN" --initdb -D "$PGDATA" -L "$PG_SHAREDIR" --username "$POSTGRES_USER" $POSTGRES_INITDB_ARGS …
```

| Constraint | Detail |
| --- | --- |
| Option form | Space-separated (`--opt value`), not `--opt=value` |
| Supported subset | `-D`/`--pgdata`, `-U`/`--username`, `-L`, `-E`/`--encoding`, locale flags, `--wal-segsize`, plus entrypoint `--waldir` when `POSTGRES_INITDB_WALDIR` is set |
| Unsupported in args | e.g. `-A`/`--auth`, `--data-checksums` — error at initdb time |
| Password | Not on the initdb CLI; applied later with `ALTER ROLE` |
| Superuser source | `--username` only (no nss_wrapper / `/etc/passwd` lookup) |

Creates the usual `postgres`, `template0`, and `template1` databases; superuser name comes from `POSTGRES_USER`.

### Host auth and password reality

| Behavior | Detail |
| --- | --- |
| Catalog password | Still stored when `POSTGRES_PASSWORD` is non-empty |
| Default host HBA | `host all all all trust` unless `POSTGRES_HOST_AUTH_METHOD` overrides |
| Password methods (`scram-sha-256`, `md5`, …) | TCP logins hit unported `fetch_role_password` → FATAL *seam not installed*; entrypoint warns if those methods are forced |
| Local / unix socket | Init client uses socket under `/var/run/postgresql` with trust-friendly defaults for setup |

Empty password is allowed only with `POSTGRES_HOST_AUTH_METHOD=trust`. Trust always prints the large upstream-style WARNING on first boot.

### Old data layout (PG 18+)

If `PGDATA` is `/var/lib/postgresql/$PG_MAJOR/docker` and leftover `PG_VERSION` trees (or an unused `/var/lib/postgresql/data` mount) are found, init aborts with the docker-library “in 18+, … pg_ctlcluster” error and suggests mounting at `/var/lib/postgresql` for upgrade-friendly layout.

## Init scripts: `/docker-entrypoint-initdb.d`

First boot only. Directory must be listable (`ls /docker-entrypoint-initdb.d/`). Scripts run against a **socket-only** temp server (`listen_addresses=''`) as `POSTGRES_USER`, default db `POSTGRES_DB` when set.

| Pattern | Action |
| --- | --- |
| `*.sh` (executable) | Execute |
| `*.sh` (not executable) | Source into the entrypoint shell |
| `*.sql` | `psql -f` |
| `*.sql.gz` | `gunzip -c` → `psql` |
| `*.sql.xz` | `xzcat` → `psql` |
| `*.sql.zst` | `zstd -dc` → `psql` |
| other | Logged as ignored |

`psql` flags: `-v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --no-password --no-psqlrc`, with `PGHOST`/`PGHOSTADDR` cleared so the default socket path applies. Image includes `gzip`, `xz-utils`, and `zstd` for compressed SQL.

```bash
docker run --rm \
  -e POSTGRES_PASSWORD=secret \
  -e POSTGRES_USER=app \
  -e POSTGRES_DB=appdb \
  -v "$PWD/initdb.d:/docker-entrypoint-initdb.d:ro" \
  -p 5432:5432 \
  pgrust
```

## Injected server options

Applied to **both** the temporary init server and the final postmaster. User CMD flags are preserved after these opts.

| Source | Setting |
| --- | --- |
| `ulimit -s` | `65520` (best-effort; failures ignored) |
| `RUST_MIN_STACK` | `33554432` if unset |
| `PGRUST_SERVER_OPTS` | `-c io_method=sync` |
| | `-c max_stack_depth=60000` |
| | `-c unix_socket_directories=/var/run/postgresql` |
| Temp server only | `-c listen_addresses=''` |
| Final server only | `-c listen_addresses='*'` |

Final rewrite when CMD is `postgres` (not help/version):

```bash
exec "$PGRUST_BIN" -D "$PGDATA" \
  -c io_method=sync \
  -c max_stack_depth=60000 \
  -c unix_socket_directories=/var/run/postgresql \
  -c listen_addresses='*' \
  "$@"
```

Non-`postgres` commands (for example an explicit `psql`, or help/version paths) are `exec`’d unchanged. Leading flags alone (`docker run … pgrust -c …`) are rewritten as `postgres -c …` first.

`io_method=sync` and the stack settings are required for pgrust boot; the entrypoint injects them so the official `CMD ["postgres"]` contract still works without callers adding flags.

## Image paths and build wiring

| Path | Content |
| --- | --- |
| `/usr/local/bin/pgrust-postgres` | pgrust release binary (server + `--initdb`) |
| `/usr/lib/postgresql/18/bin/psql` | C client only |
| `/usr/share/postgresql/18` | Bootstrap templates, config samples; `timezone` → `/usr/share/zoneinfo` symlink |
| `/var/lib/postgresql/data` | Default `PGDATA` volume |
| `/var/run/postgresql` | Unix sockets (aligned with Debian `psql`) |
| `/docker-entrypoint-initdb.d` | Empty dir for first-init scripts |
| `/usr/local/bin/docker-entrypoint.sh` | Entrypoint |

Build bake-in: `PGRUST_PGSHAREDIR=/usr/share/postgresql/18` during `cargo build --release --locked --bin postgres`. No C `postgres` or C `initdb` in the final image.

`.dockerignore` keeps the build context lean (drops `/target`, `/docs`, `/scripts`, `.git`, etc.) while keeping crate sources and fixtures needed to compile.

## Verification

<Check>
After first boot, logs include `PostgreSQL init process complete; ready for start up.` Then:

```bash
psql postgres://postgres:secret@localhost:5432/postgres \
  -c "select version(), 1 + 1 as two"
```

Or in-container: `docker exec -e PGPASSWORD=secret <ctr> psql -h 127.0.0.1 -U postgres -c '\q'`.
</Check>

## Failure modes

| Symptom | Cause / fix |
| --- | --- |
| Exit: superuser password not specified | Set `POSTGRES_PASSWORD` or `POSTGRES_HOST_AUTH_METHOD=trust` |
| FATAL *seam not installed: fetch_role_password* on TCP | Host HBA is password-based; use default/trust host auth until password auth is ported |
| Unsupported initdb option error | Strip `-A`, `--data-checksums`, or other unsupported `POSTGRES_INITDB_ARGS`; use space-separated form |
| `both X and X_FILE are set` | Pass only the env var or only the `_FILE` secret |
| Temp server timeout / exit during startup | Check `PGDATA` permissions, share dir (`-L` / `PG_SHAREDIR`), stack/`io_method` injection path |
| Old databases error on 18+ layout | Re-home volume mounts per the entrypoint message; do not leave stray `PG_VERSION` trees under `/var/lib/postgresql` |
| Init scripts not run | Only on empty `PGDATA` (no `PG_VERSION`); wrong mount or pre-existing volume |
| Socket connection failures during init | `unix_socket_directories` must be `/var/run/postgresql` (entrypoint injects this) |

## Related pages

<CardGroup>
  <Card title="Docker" href="/docker">
    Run the published or locally built image and confirm first-boot init.
  </Card>
  <Card title="initdb options reference" href="/initdb-reference">
    Supported `--initdb` flags and space-separated parsing used by `POSTGRES_INITDB_ARGS`.
  </Card>
  <Card title="Configuration reference" href="/configuration-reference">
    `io_method`, stack settings, listen addresses, and build-time path env vars.
  </Card>
  <Card title="Run the server" href="/run-server">
    Non-Docker postmaster start with the same mandatory GUCs.
  </Card>
  <Card title="Troubleshooting" href="/troubleshooting">
    Missing `io_method=sync`, stack overflows, share/tz path failures, and related boot issues.
  </Card>
</CardGroup>
