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>
114 lines
5.8 KiB
Markdown
114 lines
5.8 KiB
Markdown
# peo4c
|
|
|
|
[neo4j-browser](https://github.com/neo4j/neo4j-browser) running on PostgreSQL, using
|
|
[Apache AGE](https://github.com/apache/age) as the graph engine.
|
|
|
|
Everything ships in one container image, together with
|
|
[pgAdmin](https://www.pgadmin.org/) 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
|
|
|
|
```sh
|
|
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](https://hub.docker.com/_/postgres). `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`](bridge/peo4c)
|
|
(only `psycopg` 3). To run it outside the container against any AGE-enabled
|
|
PostgreSQL:
|
|
|
|
```sh
|
|
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`](tests/smoke_test.py) runs end-to-end checks through the official
|
|
Neo4j Python driver against a running instance:
|
|
|
|
```sh
|
|
pip install neo4j
|
|
python tests/smoke_test.py neo4j://localhost:7687 postgres secret
|
|
```
|