Skip to content

Commit 0b7069a

Browse files
committed
Add fast-startup PostgreSQL Dockerfile for testing
Pre-initialize the database at Docker build time so container startup only needs to launch the postgres process — no initdb, no entrypoint scripts. Combined with aggressive durability-off settings (fsync=off, synchronous_commit=off, wal_level=minimal), this cuts container startup from ~1-2s to near-instant. Key changes: - Dockerfile.postgres: pre-initialized PG 18 image with test-tuned config - docker-compose.yml: build from Dockerfile.postgres instead of pulling - internal/sqltest/docker/postgres.go: auto-build the fast image on first test run, pipe Dockerfile via stdin to avoid sending build context https://claude.ai/code/session_018Godbm3DFdWJf5jc7NU8Uq
1 parent 024b843 commit 0b7069a

File tree

3 files changed

+132
-14
lines changed

3 files changed

+132
-14
lines changed

Dockerfile.postgres

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# Fast-startup PostgreSQL for sqlc testing
2+
#
3+
# This image pre-initializes the database at build time so that container
4+
# startup only needs to launch the postgres process — no initdb, no
5+
# entrypoint scripts, no first-run setup.
6+
#
7+
# Build:
8+
# docker build -f Dockerfile.postgres -t sqlc-postgres .
9+
#
10+
# Run:
11+
# docker run -d -p 5432:5432 sqlc-postgres
12+
#
13+
# For fastest I/O, mount the data directory on tmpfs:
14+
# docker run -d -p 5432:5432 --tmpfs /var/lib/postgresql/data:rw,noexec,nosuid,size=256m sqlc-postgres
15+
#
16+
# Connection URI:
17+
# postgres://postgres:mysecretpassword@localhost:5432/postgres?sslmode=disable
18+
19+
ARG PG_VERSION=18
20+
FROM postgres:${PG_VERSION}
21+
22+
ENV POSTGRES_USER=postgres \
23+
POSTGRES_PASSWORD=mysecretpassword \
24+
POSTGRES_DB=postgres \
25+
PGDATA=/var/lib/postgresql/data
26+
27+
# Pre-initialize the database at build time and apply speed-optimized
28+
# configuration. This eliminates the ~1-2s initdb cost on every container start.
29+
RUN set -eux; \
30+
mkdir -p "$PGDATA"; \
31+
chown postgres:postgres "$PGDATA"; \
32+
echo "$POSTGRES_PASSWORD" > /tmp/pwfile; \
33+
gosu postgres initdb \
34+
--username="$POSTGRES_USER" \
35+
--pwfile=/tmp/pwfile \
36+
-D "$PGDATA"; \
37+
rm /tmp/pwfile; \
38+
\
39+
# --- Performance settings (unsafe for production, ideal for tests) --- \
40+
{ \
41+
echo ""; \
42+
echo "# === sqlc test optimizations ==="; \
43+
echo "listen_addresses = '*'"; \
44+
echo "fsync = off"; \
45+
echo "synchronous_commit = off"; \
46+
echo "full_page_writes = off"; \
47+
echo "max_connections = 200"; \
48+
echo "shared_buffers = 128MB"; \
49+
echo "wal_level = minimal"; \
50+
echo "max_wal_senders = 0"; \
51+
echo "max_wal_size = 256MB"; \
52+
echo "checkpoint_timeout = 30min"; \
53+
echo "log_min_messages = FATAL"; \
54+
echo "log_statement = none"; \
55+
} >> "$PGDATA/postgresql.conf"; \
56+
\
57+
# --- Allow password auth from any host --- \
58+
echo "host all all 0.0.0.0/0 scram-sha-256" >> "$PGDATA/pg_hba.conf"; \
59+
echo "host all all ::/0 scram-sha-256" >> "$PGDATA/pg_hba.conf"
60+
61+
EXPOSE 5432
62+
63+
# SIGINT = "fast shutdown": disconnects clients and exits cleanly without
64+
# requiring crash recovery on the next start. Much faster than the default
65+
# SIGTERM ("smart shutdown") which waits for all clients to disconnect.
66+
STOPSIGNAL SIGINT
67+
68+
USER postgres
69+
70+
# Start postgres directly, bypassing docker-entrypoint.sh entirely.
71+
CMD ["postgres"]

docker-compose.yml

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,9 @@ services:
1111
MYSQL_ROOT_HOST: '%'
1212

1313
postgresql:
14-
image: "postgres:16"
14+
build:
15+
context: .
16+
dockerfile: Dockerfile.postgres
1517
ports:
1618
- "5432:5432"
1719
restart: always
18-
environment:
19-
POSTGRES_DB: postgres
20-
POSTGRES_PASSWORD: mysecretpassword
21-
POSTGRES_USER: postgres

internal/sqltest/docker/postgres.go

Lines changed: 58 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
package docker
22

33
import (
4+
"bytes"
45
"context"
56
"fmt"
67
"log/slog"
8+
"os"
79
"os/exec"
10+
"path/filepath"
811
"strings"
912
"time"
1013

@@ -13,6 +16,8 @@ import (
1316

1417
var postgresHost string
1518

19+
const postgresImageName = "sqlc-postgres"
20+
1621
func StartPostgreSQLServer(c context.Context) (string, error) {
1722
if err := Installed(); err != nil {
1823
return "", err
@@ -38,11 +43,57 @@ func StartPostgreSQLServer(c context.Context) (string, error) {
3843
return data, nil
3944
}
4045

46+
// findRepoRoot walks up from the current directory to find the directory
47+
// containing go.mod, which is the repository root.
48+
func findRepoRoot() (string, error) {
49+
dir, err := os.Getwd()
50+
if err != nil {
51+
return "", err
52+
}
53+
for {
54+
if _, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil {
55+
return dir, nil
56+
}
57+
parent := filepath.Dir(dir)
58+
if parent == dir {
59+
return "", fmt.Errorf("could not find repo root (go.mod)")
60+
}
61+
dir = parent
62+
}
63+
}
64+
65+
// buildPostgresImage builds the fast-startup PostgreSQL image from
66+
// Dockerfile.postgres. The Dockerfile requires no build context, so we
67+
// pipe it to `docker build -` to avoid sending the repo tree to the daemon.
68+
func buildPostgresImage() error {
69+
root, err := findRepoRoot()
70+
if err != nil {
71+
return err
72+
}
73+
content, err := os.ReadFile(filepath.Join(root, "Dockerfile.postgres"))
74+
if err != nil {
75+
return fmt.Errorf("read Dockerfile.postgres: %w", err)
76+
}
77+
cmd := exec.Command("docker", "build", "-t", postgresImageName, "-")
78+
cmd.Stdin = bytes.NewReader(content)
79+
output, err := cmd.CombinedOutput()
80+
if err != nil {
81+
return fmt.Errorf("docker build sqlc-postgres: %w\n%s", err, output)
82+
}
83+
return nil
84+
}
85+
86+
// postgresImageExists checks whether the sqlc-postgres image is already built.
87+
func postgresImageExists() bool {
88+
cmd := exec.Command("docker", "image", "inspect", postgresImageName)
89+
return cmd.Run() == nil
90+
}
91+
4192
func startPostgreSQLServer(c context.Context) (string, error) {
42-
{
43-
_, err := exec.Command("docker", "pull", "postgres:16").CombinedOutput()
44-
if err != nil {
45-
return "", fmt.Errorf("docker pull: postgres:16 %w", err)
93+
// Build the fast-startup image if it doesn't already exist.
94+
if !postgresImageExists() {
95+
if err := buildPostgresImage(); err != nil {
96+
return "", err
4697
}
4798
}
4899

@@ -56,14 +107,13 @@ func startPostgreSQLServer(c context.Context) (string, error) {
56107
}
57108

58109
if !exists {
110+
// The sqlc-postgres image is pre-initialized and pre-configured,
111+
// so no environment variables or extra flags are needed.
59112
cmd := exec.Command("docker", "run",
60113
"--name", "sqlc_sqltest_docker_postgres",
61-
"-e", "POSTGRES_PASSWORD=mysecretpassword",
62-
"-e", "POSTGRES_USER=postgres",
63114
"-p", "5432:5432",
64115
"-d",
65-
"postgres:16",
66-
"-c", "max_connections=200",
116+
postgresImageName,
67117
)
68118

69119
output, err := cmd.CombinedOutput()
@@ -88,7 +138,6 @@ func startPostgreSQLServer(c context.Context) (string, error) {
88138
return "", fmt.Errorf("timeout reached: %w", ctx.Err())
89139

90140
case <-ticker.C:
91-
// Run your function here
92141
conn, err := pgx.Connect(ctx, uri)
93142
if err != nil {
94143
slog.Debug("sqltest", "connect", err)

0 commit comments

Comments
 (0)