Summary
parse_database_url.py emits PORT=None when DATABASE_URL has no explicit port. That value is written into ~/.pg_service.conf (postgres) / ~/.my.cnf (mysql) as port=None, which is an invalid port, so every subsequent client connection fails. Affects both the postgres and mysql images.
This blocks the externally-managed database support being added in apppackio/apppack#173 + apppackio/apppack-backend#179, because managed Postgres/MySQL providers almost universally omit the port from their connection strings.
Root cause
postgres/bin/parse_database_url.py and mysql/bin/parse_database_url.py both do:
urllib.parse.urlparse(...).port returns None when the authority has no :port. The f-string then renders the literal string None.
postgres/bin/entrypoint.sh:
/bin/echo -e "[$NAME]\nhost=$HOST\nport=$PORT\ndbname=$NAME\nuser=$USER" > ~/.pg_service.conf
export PGSERVICE="$NAME"
Because PGSERVICE is exported before wait_for_db, libpq pulls port from the service file even though psql "$DATABASE_URL" supplies the URI (the URI has no port, so the service value is used). The port is None → connection fails immediately.
mysql/bin/entrypoint.sh has the identical defect via ~/.my.cnf.
Why it looks like a network problem
wait_for_db swallows stderr:
until psql "$DATABase_URL" -c '\q' 2>/dev/null || [ "$retries" -eq 0 ]; do
So the actual libpq error (invalid port) is discarded and the operator sees 30 rounds of PostgreSQL is unavailable followed by ERROR: PostgreSQL server did not respond. — which points at connectivity/security groups rather than URL parsing. I lost real time to this.
Reproduction
Verified live against a Neon free-tier Postgres from an AppPack app on the canary formations template (app apppack-demo, account 876020455630).
Neon's stock connection string, no port:
postgresql://USER:PW@ep-xxxx.c-4.us-east-2.aws.neon.tech/neondb?sslmode=require&channel_binding=require
apppack db dump → 30 retries, ERROR: PostgreSQL server did not respond.
Same URL with :5432 inserted:
postgresql://USER:PW@ep-xxxx.c-4.us-east-2.aws.neon.tech:5432/neondb?sslmode=require&channel_binding=require
apppack db dump → succeeds, valid PostgreSQL custom database dump - v1.16-0 (server 18.6).
The only change was the explicit port. TCP 5432 to the host was confirmed open in both cases, so this is not a networking issue.
Suggested fix
- Default the port in both
parse_database_url.py files, e.g. parsed.port or 5432 (postgres) and parsed.port or 3306 (mysql). Alternatively omit the port= line from the generated config file when parsed.port is None and let the client use its own default — arguably cleaner, since it keeps one source of truth.
- Stop hiding the failure reason in
wait_for_db. Capture stderr and print it on the final attempt so a misparsed URL is self-diagnosing rather than masquerading as a timeout.
- Add a test case with a portless
DATABASE_URL to postgres/tests/tests.sh and mysql/tests/tests.sh — this is the default shape for every hosted provider, so it should be covered.
Note on scope
Managed AppPack databases are unaffected: the DATABASE_URL generated for RDS always carries an explicit port, which is why this has stayed latent. It only surfaces for externally-managed databases.
Summary
parse_database_url.pyemitsPORT=NonewhenDATABASE_URLhas no explicit port. That value is written into~/.pg_service.conf(postgres) /~/.my.cnf(mysql) asport=None, which is an invalid port, so every subsequent client connection fails. Affects both thepostgresandmysqlimages.This blocks the externally-managed database support being added in apppackio/apppack#173 + apppackio/apppack-backend#179, because managed Postgres/MySQL providers almost universally omit the port from their connection strings.
Root cause
postgres/bin/parse_database_url.pyandmysql/bin/parse_database_url.pyboth do:urllib.parse.urlparse(...).portreturnsNonewhen the authority has no:port. The f-string then renders the literal stringNone.postgres/bin/entrypoint.sh:Because
PGSERVICEis exported beforewait_for_db, libpq pullsportfrom the service file even thoughpsql "$DATABASE_URL"supplies the URI (the URI has no port, so the service value is used). The port isNone→ connection fails immediately.mysql/bin/entrypoint.shhas the identical defect via~/.my.cnf.Why it looks like a network problem
wait_for_dbswallows stderr:So the actual libpq error (invalid port) is discarded and the operator sees 30 rounds of
PostgreSQL is unavailablefollowed byERROR: PostgreSQL server did not respond.— which points at connectivity/security groups rather than URL parsing. I lost real time to this.Reproduction
Verified live against a Neon free-tier Postgres from an AppPack app on the canary formations template (app
apppack-demo, account 876020455630).Neon's stock connection string, no port:
apppack db dump→ 30 retries,ERROR: PostgreSQL server did not respond.Same URL with
:5432inserted:apppack db dump→ succeeds, validPostgreSQL custom database dump - v1.16-0(server 18.6).The only change was the explicit port. TCP 5432 to the host was confirmed open in both cases, so this is not a networking issue.
Suggested fix
parse_database_url.pyfiles, e.g.parsed.port or 5432(postgres) andparsed.port or 3306(mysql). Alternatively omit theport=line from the generated config file whenparsed.portisNoneand let the client use its own default — arguably cleaner, since it keeps one source of truth.wait_for_db. Capture stderr and print it on the final attempt so a misparsed URL is self-diagnosing rather than masquerading as a timeout.DATABASE_URLtopostgres/tests/tests.shandmysql/tests/tests.sh— this is the default shape for every hosted provider, so it should be covered.Note on scope
Managed AppPack databases are unaffected: the DATABASE_URL generated for RDS always carries an explicit port, which is why this has stayed latent. It only surfaces for externally-managed databases.