Skip to content

parse_database_url.py emits PORT=None for URLs without an explicit port, breaking all connections #4

Description

@blsmth

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:

'PORT': parsed.port,

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

  1. 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.
  2. 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.
  3. 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions