A single container image with: - neo4j-browser built from source and served on :7474 - PostgreSQL with Apache AGE (based on apache/age) - a Python bridge speaking Bolt 4.4/5.0-5.4 over TCP and WebSocket on :7687 that runs Cypher through AGE's cypher() function, emulates the Neo4j procedures neo4j-browser relies on, and maps databases to AGE graphs Includes workarounds for AGE limitations (parameters are inlined as literals, `id(x) IN [...]` and the browser's expand-node query are rewritten) and an end-to-end smoke test using the Neo4j Python driver. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
4.8 KiB
peo4c
neo4j-browser running on PostgreSQL, using Apache AGE as the graph engine.
Everything ships in one container image:
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 5432:5432 \
-v peo4c-data:/var/lib/postgresql peo4c
Open http://localhost:7474/ and log in with your PostgreSQL credentials:
user postgres, password the value of POSTGRES_PASSWORD. Bolt clients (the Neo4j
drivers, cypher-shell) can connect to neo4j://localhost:7687 the same way, and
psql works on port 5432.
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 namedneo4jis created on first start (PEO4C_DEFAULT_GRAPH).CREATE DATABASE xandDROP DATABASE xon thesystemdatabase callcreate_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 finalRETURNclause 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. |
Build arguments: AGE_IMAGE (base image, default apache/age:release_PG18_1.8.0) and
NEO4J_BROWSER_REF (neo4j-browser commit to build).
Limitations
This is a first version. Things that do not work (yet):
RETURN *,EXPLAINandPROFILE.- 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