diff --git a/.dockerignore b/.containerignore
similarity index 100%
rename from .dockerignore
rename to .containerignore
diff --git a/Dockerfile b/Containerfile
similarity index 83%
rename from Dockerfile
rename to Containerfile
index d56a063..9c0288c 100644
--- a/Dockerfile
+++ b/Containerfile
@@ -46,11 +46,19 @@ RUN python3 -m venv /opt/pgadmin \
# License notices: see THIRD_PARTY_NOTICES.md. neo4j-browser is GPL-3.0, so its
# complete source is shipped next to the license.
-COPY docker/collect-python-licenses.py /tmp/
-COPY docker/licenses/ /tmp/licenses/
+COPY container/collect-python-licenses.py /tmp/
+COPY container/licenses/ /tmp/licenses/
RUN /opt/pgadmin/bin/python /tmp/collect-python-licenses.py \
- /usr/share/doc/peo/pgadmin-python-licenses /tmp/licenses \
- && rm -r /tmp/collect-python-licenses.py /tmp/licenses
+ /usr/share/doc/peo/pgadmin-python-licenses /tmp/licenses
+
+# peo itself, using Debian's psycopg and the other dependencies from PyPI.
+COPY pyproject.toml README.md LICENSE /tmp/peo/
+COPY peo /tmp/peo/peo
+RUN python3 -m venv --system-site-packages /opt/peo/venv \
+ && /opt/peo/venv/bin/pip install --no-cache-dir "/tmp/peo[browser]" \
+ && /opt/peo/venv/bin/python /tmp/collect-python-licenses.py \
+ /usr/share/doc/peo/peo-python-licenses /tmp/licenses \
+ && rm -r /tmp/peo /tmp/collect-python-licenses.py /tmp/licenses
COPY LICENSE THIRD_PARTY_NOTICES.md /usr/share/doc/peo/
COPY --from=browser /src/LICENSE /src/NOTICE.txt /src/LICENSES.txt /neo4j-browser-source.tar.gz \
/usr/share/doc/peo/neo4j-browser/
@@ -59,15 +67,9 @@ ADD https://raw.githubusercontent.com/apache/age/${AGE_SOURCE_REF}/LICENSE \
/usr/share/doc/peo/apache-age/
COPY --from=browser /src/dist /opt/peo/browser
-# peo itself, using Debian's psycopg.
-COPY pyproject.toml README.md LICENSE /tmp/peo/
-COPY peo /tmp/peo/peo
-RUN python3 -m venv --system-site-packages /opt/peo/venv \
- && /opt/peo/venv/bin/pip install --no-cache-dir --no-deps /tmp/peo \
- && rm -r /tmp/peo
-COPY docker/initdb/ /docker-entrypoint-initdb.d/
-COPY docker/pgadmin/config_distro.py /opt/peo/pgadmin/config_distro.py
-COPY docker/peo-entrypoint.sh /usr/local/bin/peo-entrypoint.sh
+COPY container/initdb/ /docker-entrypoint-initdb.d/
+COPY container/pgadmin/config_distro.py /opt/peo/pgadmin/config_distro.py
+COPY container/peo-entrypoint.sh /usr/local/bin/peo-entrypoint.sh
ENV PEO_DEFAULT_GRAPH=neo4j \
PEO_BROWSER_DIR=/opt/peo/browser \
diff --git a/README.md b/README.md
index 6cb92e4..eab8540 100644
--- a/README.md
+++ b/README.md
@@ -32,9 +32,17 @@ The `binary` extra pulls in psycopg's prebuilt wheels; leave it out to use a psy
built against your system's libpq. `python -m peo` works the same as `peo`.
Neo4j clients can now connect to `neo4j://localhost:7687` with a PostgreSQL user name
-and password. peo does not include neo4j-browser itself: build it and point
-`PEO_BROWSER_DIR` at its `dist` directory to serve it on , or
-use the container image.
+and password.
+
+peo does not include neo4j-browser itself. To serve it, install the `browser` extra
+([Starlette](https://www.starlette.io/) and [uvicorn](https://www.uvicorn.org/)),
+build neo4j-browser and point `PEO_BROWSER_DIR` at its `dist` directory; it is then
+served on . The container image does all of this for you.
+
+```sh
+pip install 'peo[binary,browser]'
+PEO_PG_HOST=localhost PEO_PG_DATABASE=postgres PEO_BROWSER_DIR=/path/to/neo4j-browser/dist peo
+```
## Configuration
@@ -48,7 +56,7 @@ peo is configured through environment variables:
| `PEO_DEFAULT_GRAPH` | `neo4j` | Graph used when a client does not pick a database. |
| `PEO_BOLT_PORT` | `7687` | Port the Bolt server listens on (TCP and WebSocket). |
| `PEO_HTTP_PORT` | `7474` | Port neo4j-browser is served on, when `PEO_BROWSER_DIR` is set. |
-| `PEO_BROWSER_DIR` | (unset) | neo4j-browser `dist` directory to serve. |
+| `PEO_BROWSER_DIR` | (unset) | neo4j-browser `dist` directory to serve; needs the `browser` extra. |
| `PEO_ADVERTISED_BOLT_PORT` | `$PEO_BOLT_PORT` | Bolt port advertised to the browser; set it when clients reach the Bolt port on another port. |
| `PEO_ADVERTISED_HOST` | `localhost` | Host reported in `SHOW DATABASES` and to clients that do not send a routing address. |
| `PEO_LOG_LEVEL` | `INFO` | `DEBUG` logs every generated SQL statement. |
@@ -151,7 +159,9 @@ pgAdmin comes with the bundled PostgreSQL server already registered as "peo";
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.
+Any OCI-compatible build tool works. With Docker, pass the file name explicitly:
+`docker build -f Containerfile -t peo .`; `docker run` takes the same options as
+`podman run`.
Besides peo's own variables above, the image uses these:
@@ -186,11 +196,12 @@ This is a first version. Things that do not work (yet):
## Development
-peo's only dependency is `psycopg` 3. To work on it, install it in editable mode;
-`python -m build` (or `uv build`) builds an sdist and wheel:
+peo depends on `psycopg` 3 and `websockets`, plus Starlette and uvicorn for the
+`browser` extra. To work on it, install it in editable mode; `python -m build` (or
+`uv build`) builds an sdist and wheel:
```sh
-pip install -e '.[binary]'
+pip install -e '.[binary,browser]'
```
[`tests/smoke_test.py`](tests/smoke_test.py) runs end-to-end checks through the official
diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md
index ce9d7fc..b229ae3 100644
--- a/THIRD_PARTY_NOTICES.md
+++ b/THIRD_PARTY_NOTICES.md
@@ -1,7 +1,7 @@
# Third-party notices
-peo's own code (the `peo/`, `docker/` and `tests/` directories, the
-`Dockerfile` and `pyproject.toml`) is licensed under the GNU General Public
+peo's own code (the `peo/`, `container/` and `tests/` directories, the
+`Containerfile` and `pyproject.toml`) is licensed under the GNU General Public
License v3.0 or later; see [LICENSE](LICENSE).
The container image built from this repository also contains the third-party
@@ -11,11 +11,12 @@ the image is built. Inside the image, license texts are collected under
| Component | License | Copyright | License text in the image |
| --- | --- | --- | --- |
-| [neo4j-browser](https://github.com/neo4j/neo4j-browser), built unmodified from the commit pinned in the `Dockerfile` | GPL-3.0-or-later | Neo4j Sweden AB | `/usr/share/doc/peo/neo4j-browser/` (`LICENSE`, `NOTICE.txt`) |
+| [neo4j-browser](https://github.com/neo4j/neo4j-browser), built unmodified from the commit pinned in the `Containerfile` | GPL-3.0-or-later | Neo4j Sweden AB | `/usr/share/doc/peo/neo4j-browser/` (`LICENSE`, `NOTICE.txt`) |
| JavaScript libraries bundled into neo4j-browser | various (MIT, Apache-2.0, BSD, ...) | their respective authors | `/usr/share/doc/peo/neo4j-browser/LICENSES.txt` |
| [Apache AGE](https://github.com/apache/age), from the `apache/age` base image | Apache-2.0 | The Apache Software Foundation; portions Bitnine Co., Ltd. | `/usr/share/doc/peo/apache-age/` (`LICENSE`, `NOTICE`) |
| [PostgreSQL](https://www.postgresql.org/), from the `apache/age` base image | PostgreSQL License | The PostgreSQL Global Development Group, The Regents of the University of California | `/usr/share/doc/postgresql-18/copyright` |
| Debian GNU/Linux base system and packages, including Python 3 and `python3-psycopg` (LGPL-3.0) | various | their respective authors | `/usr/share/doc//copyright` |
+| Python packages peo depends on, installed from PyPI: `websockets` (BSD-3-Clause), `starlette` (BSD-3-Clause), `uvicorn` (BSD-3-Clause) and their dependencies | various (BSD-3-Clause, MIT, PSF-2.0) | their respective authors | `*.dist-info/` under `/opt/peo/venv/lib/python3*/site-packages/`, or `/usr/share/doc/peo/peo-python-licenses/` for packages whose wheel lacks a license file |
| [pgAdmin 4](https://www.pgadmin.org/), installed from PyPI | PostgreSQL License | The pgAdmin Development Team | `/opt/pgadmin/app/LICENSE` |
| Python packages pgAdmin depends on, and `gunicorn` (MIT) | various | their respective authors | `*.dist-info/` under `/opt/pgadmin/lib/python3*/site-packages/`, or `/usr/share/doc/peo/pgadmin-python-licenses/` for packages whose wheel lacks a license file |
@@ -26,9 +27,10 @@ the image is built. Inside the image, license texts are collected under
it as `/usr/share/doc/peo/neo4j-browser/neo4j-browser-source.tar.gz`, an
archive of the exact commit that was built. Its dependencies are pinned by
the `yarn.lock` inside that archive, and the build steps are in this
- repository's `Dockerfile`.
+ repository's `Containerfile`.
- **peo** itself ships as Python source code in the `/opt/peo/venv/`
- virtual environment. This repository has the rest.
+ virtual environment, next to the Python packages it depends on. This
+ repository has the rest.
- **Debian packages:** source is available from
[sources.debian.org](https://sources.debian.org/) and the Debian archive.
diff --git a/docker/collect-python-licenses.py b/container/collect-python-licenses.py
similarity index 89%
rename from docker/collect-python-licenses.py
rename to container/collect-python-licenses.py
index dc94990..dc15f9b 100644
--- a/docker/collect-python-licenses.py
+++ b/container/collect-python-licenses.py
@@ -6,13 +6,16 @@ Most wheels include their license files in ``*.dist-info``. For the ones that
do not, copy its license files to ``