Files
boltage/README.md
T
Bart BroereandClaude Opus 5.5 e72af59401 Add pgAdmin to the peo4c image
Install pgAdmin 9.18 in its own virtualenv and run it under gunicorn on
port 5050 next to PostgreSQL and the bridge. On first start the
entrypoint creates the pgAdmin login (PGADMIN_DEFAULT_EMAIL, default
admin@example.com; PGADMIN_DEFAULT_PASSWORD, default POSTGRES_PASSWORD)
and registers the bundled PostgreSQL server. pgAdmin is optional
(PEO4C_PGADMIN=false) and a failure in it does not stop the container.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 11:19:58 +02:00

5.8 KiB

peo4c

neo4j-browser running on PostgreSQL, using Apache AGE as the graph engine.

Everything ships in one container image, together with pgAdmin for working with the same data in SQL:

 neo4j-browser (static files)      peo4c bridge (Python)              PostgreSQL + AGE
 ┌──────────────────────┐  Bolt   ┌──────────────────────────┐  SQL  ┌────────────────┐
 │ in your web browser  │───────▶│ Bolt server (TCP + WS)    │──────▶│ cypher('g',$$ │
 │ served on :7474      │ :7687  │ Cypher → AGE translation   │ :5432 │   ... $$)      │
 └──────────────────────┘        │ emulated Neo4j procedures │       └────────────────┘
                                 └──────────────────────────┘

Quick start

podman build -t peo4c .
podman run -d --name peo4c -e POSTGRES_PASSWORD=secret \
    -p 7474:7474 -p 7687:7687 -p 5050:5050 -p 5432:5432 \
    -v peo4c-data:/var/lib/postgresql -v peo4c-pgadmin:/var/lib/pgadmin peo4c
Port Service Log in with
7474 neo4j-browser: http://localhost:7474/ PostgreSQL user postgres and POSTGRES_PASSWORD
7687 Bolt, for Neo4j drivers and cypher-shell (neo4j://localhost:7687) same as above
5050 pgAdmin: http://localhost:5050/ admin@example.com and POSTGRES_PASSWORD
5432 PostgreSQL, for psql and other SQL clients same as neo4j-browser

pgAdmin comes with the bundled PostgreSQL server already registered as "peo4c"; it asks for the PostgreSQL password the first time you open it. AGE stores each graph as a schema of the same name, with one table per label.

docker works as a drop-in replacement for podman in the commands above.

How it works

  • Databases are AGE graphs. Every AGE graph shows up as a Neo4j database, and :use <name> switches between them. A graph named neo4j is created on first start (PEO4C_DEFAULT_GRAPH). CREATE DATABASE x and DROP DATABASE x on the system database call create_graph / drop_graph; AGE requires names of at least 3 characters.
  • Queries are sent to AGE as SELECT * FROM cypher('<graph>', $$ <query> $$) AS (...). The bridge works out the result columns from the final RETURN clause and inlines query parameters as Cypher literals.
  • Neo4j procedures that neo4j-browser relies on (dbms.components, SHOW DATABASES, db.labels, db.schema.visualization, SHOW PROCEDURES, ...) are emulated from the AGE catalog.
  • Workarounds for AGE: neo4j-browser's "expand node" query uses a pattern comprehension, and its relationship lookup uses id(n) IN [...], which AGE rejects for lists of more than one element. The bridge rewrites both into equivalent Cypher that AGE accepts.
  • Authentication is passed straight through: every Bolt connection opens its own PostgreSQL connection with the credentials you log in with.

Configuration

Variable Default Purpose
POSTGRES_PASSWORD (required) Password of the PostgreSQL superuser, see the postgres image docs. POSTGRES_USER and POSTGRES_DB work too.
PEO4C_DEFAULT_GRAPH neo4j Graph created at initialisation and used when a client does not pick a database.
PEO4C_PG_DATABASE $POSTGRES_DB PostgreSQL database holding the graphs.
PEO4C_ADVERTISED_BOLT_PORT 7687 Bolt port advertised to the browser; set it when you publish 7687 on another host port.
PEO4C_ADVERTISED_HOST localhost Host reported in SHOW DATABASES and to clients that do not send a routing address.
PEO4C_LOG_LEVEL INFO DEBUG logs every generated SQL statement.
PGADMIN_DEFAULT_EMAIL admin@example.com pgAdmin login, created on first start.
PGADMIN_DEFAULT_PASSWORD $POSTGRES_PASSWORD pgAdmin login password, set on first start.
PEO4C_PGADMIN true Set to false to not start pgAdmin.
PEO4C_PGADMIN_PORT 5050 Port pgAdmin listens on inside the container.

Build arguments: AGE_IMAGE (base image, default apache/age:release_PG18_1.8.0), NEO4J_BROWSER_REF (neo4j-browser commit to build) and PGADMIN_VERSION (default 9.18).

pgAdmin keeps its users and settings in /var/lib/pgadmin; changing PGADMIN_DEFAULT_* after the first start has no effect unless that directory is empty.

Limitations

This is a first version. Things that do not work (yet):

  • RETURN *, EXPLAIN and PROFILE.
  • Write statistics: write queries report "(no changes, no records)" even when they changed the graph.
  • Anything AGE does not support, for example multiple labels per node, pattern comprehensions, temporal and spatial types, indexes and constraints through Cypher.
  • User and role management, SHOW TRANSACTIONS, :sysinfo.
  • TLS (bolt+s://, HTTPS).
  • Each query's full result is fetched from PostgreSQL before it is streamed to the client, so very large results use memory in the bridge.

Development

The bridge is a dependency-light Python package in bridge/peo4c (only psycopg 3). To run it outside the container against any AGE-enabled PostgreSQL:

pip install 'psycopg[binary]'
cd bridge
PEO4C_PG_HOST=localhost PEO4C_PG_DATABASE=postgres PEO4C_BROWSER_DIR=/path/to/neo4j-browser/dist \
    python -m peo4c

tests/smoke_test.py runs end-to-end checks through the official Neo4j Python driver against a running instance:

pip install neo4j
python tests/smoke_test.py neo4j://localhost:7687 postgres secret