Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/build_docker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ jobs:
IMAGE_TAG: ${{ steps.get-image-tag.outputs.image_tag }}
run: |
docker build -t $REGISTRY/$REGISTRY_ALIAS/$REPOSITORY:$IMAGE_TAG .
docker build --build-arg NEPTUNE_NOTEBOOK=true -t $REGISTRY/$REGISTRY_ALIAS/$REPOSITORY:sagemaker-$IMAGE_TAG .
docker tag $REGISTRY/$REGISTRY_ALIAS/$REPOSITORY:$IMAGE_TAG $REGISTRY/$REGISTRY_ALIAS/$REPOSITORY:sagemaker-$IMAGE_TAG
docker push $REGISTRY/$REGISTRY_ALIAS/$REPOSITORY:$IMAGE_TAG
docker push $REGISTRY/$REGISTRY_ALIAS/$REPOSITORY:sagemaker-$IMAGE_TAG

Expand Down
10 changes: 0 additions & 10 deletions .github/workflows/test_build_docker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ jobs:
- name: Build Docker image
run: |
docker build -t test-image .
docker build -t test-image-neptune --build-arg NEPTUNE_NOTEBOOK=true .

- name: Scan Docker image for vulnerabilities
uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # v0.35.0
Expand All @@ -36,7 +35,6 @@ jobs:
- name: Ensure openSSL is installed
run: |
docker run --rm --entrypoint="" test-image openssl --version
docker run --rm --entrypoint="" test-image-neptune openssl --version

- name: Verify unnecessary packages are removed
run: |
Expand All @@ -48,14 +46,6 @@ jobs:
(command -v yum && echo "FAIL: yum found" && exit 1) || echo "✓ yum removed"
(command -v dnf && echo "FAIL: dnf found" && exit 1) || echo "✓ dnf removed"
'
docker run --rm --entrypoint="" test-image-neptune sh -c '
(command -v npm && echo "FAIL: npm found" && exit 1) || echo "✓ npm removed"
(command -v pnpm && echo "FAIL: pnpm found" && exit 1) || echo "✓ pnpm removed"
(command -v corepack && echo "FAIL: corepack found" && exit 1) || echo "✓ corepack removed"
(command -v python3 && echo "FAIL: python3 found" && exit 1) || echo "✓ python3 removed"
(command -v yum && echo "FAIL: yum found" && exit 1) || echo "✓ yum removed"
(command -v dnf && echo "FAIL: dnf found" && exit 1) || echo "✓ dnf removed"
'

- name: Verify server starts and responds
run: |
Expand Down
23 changes: 0 additions & 23 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -20,32 +20,9 @@ RUN yum update -y --releasever 2023.11.20260427 && \
rm -rf /var/cache/yum /var/cache/dnf

FROM base
ARG NEPTUNE_NOTEBOOK

ENV NEPTUNE_NOTEBOOK=$NEPTUNE_NOTEBOOK
ENV HOME=/graph-explorer

# Conditionally set the following environment values using +/- variable expansion
# https://docs.docker.com/reference/dockerfile/#environment-replacement
#
# If NEPTUNE_NOTEBOOK value is set then
# - GRAPH_EXP_ENV_ROOT_FOLDER = /proxy/9250/explorer
# - PROXY_SERVER_HTTP_PORT = 9250
# - LOG_STYLE = cloudwatch
# Else the values are the defaults
# - GRAPH_EXP_ENV_ROOT_FOLDER = /explorer
# - PROXY_SERVER_HTTP_PORT = 80
# - LOG_STYLE = default

ENV GRAPH_EXP_ENV_ROOT_FOLDER=${NEPTUNE_NOTEBOOK:+/proxy/9250/explorer}
ENV GRAPH_EXP_ENV_ROOT_FOLDER=${GRAPH_EXP_ENV_ROOT_FOLDER:-/explorer}

ENV PROXY_SERVER_HTTP_PORT=${NEPTUNE_NOTEBOOK:+9250}
ENV PROXY_SERVER_HTTP_PORT=${PROXY_SERVER_HTTP_PORT:-80}

ENV LOG_STYLE=${NEPTUNE_NOTEBOOK:+cloudwatch}
ENV LOG_STYLE=${LOG_STYLE:-default}

WORKDIR /
COPY . /graph-explorer/
WORKDIR /graph-explorer
Expand Down
143 changes: 143 additions & 0 deletions docs/adr/0001-unify-docker-image-remove-sagemaker-variant.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
# ADR 0001: Unify Docker Image by Removing SageMaker Variant

## Status

Accepted

## Context

Graph Explorer ships two Docker images from the same Dockerfile: a main image and a
SageMaker image built with `--build-arg NEPTUNE_NOTEBOOK=true`. The build argument
bakes different environment variable defaults into the image (port 9250 instead of 80,
cloudwatch logging, a different Vite `base` path for static assets). The SageMaker
lifecycle script already passes all these values as runtime environment variables,
making the build-time split unnecessary.

Additionally, the client requires users to manually specify a "Public or Proxy
Endpoint" URL even though the proxy server and UI are always served from the same
origin. This creates confusion and an extra configuration step that can be derived
automatically.

## Decision

Eliminate the separate SageMaker image by:

1. **Using relative asset paths** — set Vite `base: "./"` and add `<base href="./">`
to `index.html`. This makes the compiled frontend work behind any reverse proxy
without build-time knowledge of the path prefix.

2. **Using relative fetch paths** — the client fetches API routes (sparql, gremlin,
openCypher, defaultConnection, etc.) via `new URL("../sparql", document.baseURI)`
instead of constructing absolute URLs from a configured proxy endpoint. The server
mounts static files at `/explorer` and API routes at `/`, so `../` from the static
directory always resolves to the API root.

3. **Always routing through the proxy** — remove the `proxyConnection` toggle and
`url` (proxy endpoint) from the connection model. The client always sends requests
to the same-origin proxy server. The connection config simplifies to: database
endpoint, query engine, and optional IAM settings.

4. **Moving SageMaker defaults to runtime** — `process-environment.sh` reads
`NEPTUNE_NOTEBOOK=true` at container startup and writes port/log-style/SSL
settings to `.env`. The Dockerfile no longer sets these, allowing the app's
built-in defaults (port 80, default log style) to apply when the variable is absent.

5. **Publishing dual tags during transition** — CI builds one image and publishes it
under both the regular tag and the `sagemaker-*` tag, so existing lifecycle scripts
continue working without modification.

## Consequences

### Positive

- One image to build, test, scan, and publish — halves CI time for Docker.
- Users no longer need to figure out or configure the proxy server URL.
- The connection form simplifies to just the database endpoint and auth settings.
- Deployments behind arbitrary reverse proxies (not just Jupyter) work without
build-time configuration.
- Removes ~20 lines of conditional Dockerfile logic and the two-path defaultConnection
fallback hack in the client.

### Negative

- Relative paths create a fixed contract: the API root is always one directory above
the static files mount. This is enforced in one place (`server-config.ts`) so drift
is unlikely but possible.
- Legacy stored connections (localStorage) need a read-time migration:
`graphDbUrl = old.proxyConnection ? old.graphDbUrl : old.url`.
- The `sagemaker-*` tags must be published for several release cycles until existing
deployed lifecycle scripts are updated.
- The proxy server must have network access to the target database. Deployments in
restricted networks (e.g., private subnets without a NAT gateway) cannot reach
databases outside that network — even if the user's browser previously could via
direct connections. Users in this scenario need to add network routing.

### Neutral

- `NEPTUNE_NOTEBOOK` remains as a runtime convenience preset (sets port, log style,
disables SSL). It is not written to `.env` itself — only its side effects are
applied.
- Extra environment variables passed by old deployments (`PUBLIC_OR_PROXY_ENDPOINT`,
`USING_PROXY_SERVER`) are silently ignored — no errors.

## Changes Required

### Dockerfile

- Remove `ARG NEPTUNE_NOTEBOOK` and all conditional ENV logic
- Remove `GRAPH_EXP_ENV_ROOT_FOLDER`
- Remove `ENV PROXY_SERVER_HTTP_PORT` and `ENV LOG_STYLE` (defaults from `env.ts`)
- Keep `EXPOSE 80`, `EXPOSE 443`, `EXPOSE 9250`

### Frontend Build

- `vite.config.ts`: `base: "./"`
- `index.html`: add `<base href="./">`

### Client Code

- Explorers use `new URL("../sparql", document.baseURI)` etc.
- `defaultConnection.ts`: single relative fetch, remove SageMaker fallback
- `RELOAD_URL` becomes `"."`
- Remove `proxyConnection` and `url` from `ConnectionConfig`
- `graphDbUrl` is the canonical database URL field
- `fetchDatabaseRequest.ts`: always send `graph-db-connection-url` header
- Read-time normalization for legacy data

### Client UI

- Connection form: remove proxy endpoint and proxy toggle, always show "Graph
Connection URL" field and IAM toggle
- Connection display components: simplify to `graphDbUrl`

### Server

- `process-environment.sh`:
- Remove `PUBLIC_OR_PROXY_ENDPOINT` / `USING_PROXY_SERVER` handling
- When `NEPTUNE_NOTEBOOK=true`: write `PROXY_SERVER_HTTP_PORT=9250` and
`LOG_STYLE=cloudwatch` to `.env` (respecting explicit overrides), force SSL off
- Stop writing `NEPTUNE_NOTEBOOK` to `.env`

### CI

- Build one image, publish under regular + `sagemaker-*` tags
- Remove `--build-arg NEPTUNE_NOTEBOOK=true` step

### Lifecycle Script

Existing lifecycle scripts continue working unchanged — they pull `sagemaker-*` tags
(now an alias for the regular image) and pass `PUBLIC_OR_PROXY_ENDPOINT` /
`USING_PROXY_SERVER` env vars which the unified image silently ignores.

The bundled example script in this repo is updated to:
- Pull regular tag instead of `sagemaker-*`
- Remove `PUBLIC_OR_PROXY_ENDPOINT` and `USING_PROXY_SERVER` from `docker run`

### Documentation

- `docs/references/configuration.md`
- `docs/guides/deploy-to-ecs-fargate.md`
- `docs/guides/deploy-to-sagemaker.md`
- `docs/guides/deploy-with-docker.md`
- `docs/features/connections.md`
- `docs/architecture.md`
7 changes: 4 additions & 3 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,16 @@ Graph Explorer is a client-heavy web application with a thin backend proxy. The
graph LR
Browser["Browser\n(React)"] -- HTTP --> Proxy["Proxy Server\n(Express)"]
Proxy -- HTTP --> DB["Graph Database\n(Neptune, etc.)"]
Browser -. direct .-> DB
Browser -- persistence --> IDB["IndexedDB\n(localforage)"]
```

The React client constructs queries and sends them through the proxy server, which forwards requests to the graph database. When connecting to Amazon Neptune, the proxy signs requests with AWS SigV4 credentials. For non-Neptune databases, the proxy is optional — the client can connect directly to a publicly accessible endpoint (shown as the dotted line above).
The React client constructs queries and sends them through the proxy server using relative URLs, which forwards requests to the graph database. When connecting to Amazon Neptune, the proxy signs requests with AWS SigV4 credentials.

The proxy does not store any user data — all preferences, connections, and query history live in the browser's IndexedDB.

This split exists because browsers cannot perform SigV4 signing directly (it requires AWS credentials that should not be exposed to the client), and because the proxy can run inside a VPC alongside the database while the browser runs outside it.
This architecture allows the app to work behind any reverse proxy (SageMaker, custom paths) without build-time configuration, since the client resolves API endpoints relative to its own location. The proxy can run inside a VPC alongside the database while the browser runs outside it.

Because all requests flow through the proxy, the server must have network access to the target database. If the server is in a restricted network (e.g., a private subnet with no NAT gateway), it will not be able to reach databases outside that network even if the user's browser could reach them directly.

## Monorepo Structure

Expand Down
13 changes: 5 additions & 8 deletions docs/features/connections.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,11 @@ For guides on connecting to specific databases, see [Connecting to databases](..

- **Name:** Enter a name for your connection (e.g., `MyNeptuneCluster`).
- **Query Language:** Choose a query language that corresponds to your graph database.
- **Public or proxy endpoint:** Provide the publicly accessible endpoint URL for a graph database, e.g., Gremlin Server. If connecting to Amazon Neptune, then provide a proxy endpoint URL that is accessible from outside the VPC, e.g., EC2.
- **Note:** For connecting to Amazon Neptune, ensure that the graph connection URL is in the format `https://[NEPTUNE_ENDPOINT]:8182`, and that the proxy endpoint URL is either `https://[EC2_PUBLIC_HOSTNAME]:443` or `http://[EC2_PUBLIC_HOSTNAME]:80`, depending on the protocol used. Ensure that you don't end either of the URLs with `/`.
- **Using proxy server:** Check this box if using a proxy endpoint.
- **Graph connection URL:** Provide the endpoint for the graph database
- **AWS IAM Auth Enabled:** Check this box if connecting to Amazon Neptune using IAM Auth and SigV4 signed requests
- **Service Type:** Choose the service type
- **AWS Region:** Specify the AWS region where the Neptune cluster is hosted (e.g., us-east-1)
- **Fetch Timeout:** Specify the timeout for the fetch request
- **Graph Connection URL:** Provide the endpoint URL for your graph database (e.g., `https://[NEPTUNE_ENDPOINT]:8182`). Ensure that the URL does not end with `/`.
- **AWS IAM Auth Enabled:** Check this box if connecting to Amazon Neptune using IAM Auth and SigV4 signed requests.
- **Service Type:** Choose the service type (`neptune-db` or `neptune-graph`).
- **AWS Region:** Specify the AWS region where the Neptune cluster is hosted (e.g., us-east-1).
- **Fetch Timeout:** Specify the timeout for the fetch request.
- **Neighbor Expansion Limit:** Specify the default limit for neighbor expansion. This will override the app setting for neighbor expansion.

## Available Connections
Expand Down
9 changes: 0 additions & 9 deletions docs/guides/deploy-to-ecs-fargate.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,14 +107,6 @@ After the request is processed, the console will return you to your certificate
"name": "IAM",
"value": "false"
},
{
"name": "USING_PROXY_SERVER",
"value": "true"
},
{
"name": "PUBLIC_OR_PROXY_ENDPOINT",
"value": "https://{FQDN_from_step3}"
},
{
"name": "HOST",
"value": "localhost"
Expand Down Expand Up @@ -164,7 +156,6 @@ After the request is processed, the console will return you to your certificate
- `GRAPH_TYPE`: The query language for your initial connection.
- `IAM`: Set this to `true` to use SigV4 signed requests, if your Neptune cluster has IAM db authentication enabled.
- `GRAPH_CONNECTION_URL`: Set this as `https://{NEPTUNE_ENDPOINT}:8182`.
- `PUBLIC_OR_PROXY_ENDPOINT`: Set this as `https://{Domain name set in Step 5 of "Request an ACM Public Certificate"}`.
- `SERVICE_TYPE`: Set this as `neptune-db` for Neptune database or `neptune-graph` for Neptune Analytics.
6. Click **Create**.

Expand Down
6 changes: 6 additions & 0 deletions docs/guides/deploy-to-sagemaker.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ When the notebook has been started and is in "Ready" state, you can access Graph
https://graph-explorer-notebook-name.notebook.us-west-2.sagemaker.aws/proxy/9250/explorer/
```

## Network Requirements

All database requests from Graph Explorer are routed through the proxy server running on the SageMaker notebook instance. This means the instance must have network access to any database you want to explore.

If your notebook instance is in a private subnet without a NAT gateway or internet gateway, it will not be able to reach databases outside the VPC. To connect to external databases, ensure the instance has the appropriate network routing (VPC peering, NAT gateway, transit gateway, etc.).

## Minimum Database Permissions

By default, the permission policy for the IAM role of the SageMaker instance will have full access to the Neptune Database or Neptune Analytics instance. This means queries executed within Graph Explorer could contain mutations.
Expand Down
19 changes: 5 additions & 14 deletions docs/guides/deploy-to-sagemaker/install-graph-explorer-lc.sh
Original file line number Diff line number Diff line change
Expand Up @@ -130,17 +130,14 @@ LATEST_ECR_RELEASE=$(curl -k -H "Authorization: Bearer $ECR_TOKEN" https://publi

echo "Pulling and starting graph-explorer..."
if [[ ${EXPLORER_VERSION} == "" ]]; then
EXPLORER_ECR_TAG=sagemaker-${LATEST_ECR_RELEASE}
EXPLORER_ECR_TAG=${LATEST_ECR_RELEASE}
else
if [[ ${EXPLORER_VERSION//./} -ge 140 ]]; then
EXPLORER_ECR_TAG=sagemaker-${EXPLORER_VERSION}
elif [[ ${EXPLORER_VERSION} == *latest* ]]; then
EXPLORER_ECR_TAG=sagemaker-latest-SNAPSHOT
if [[ ${EXPLORER_VERSION} == *latest* ]]; then
EXPLORER_ECR_TAG=latest-SNAPSHOT
elif [[ ${EXPLORER_VERSION} == *dev* ]]; then
EXPLORER_ECR_TAG=sagemaker-dev
EXPLORER_ECR_TAG=dev
else
echo "Specified Graph Explorer version does not support use on SageMaker. Defaulting to latest release."
EXPLORER_ECR_TAG=sagemaker-${LATEST_ECR_RELEASE}
EXPLORER_ECR_TAG=${EXPLORER_VERSION}
fi
fi
echo "Using explorer image tag: ${EXPLORER_ECR_TAG}"
Expand All @@ -155,13 +152,10 @@ start_graph_explorer_with_cw_logs() {
--log-opt awslogs-multiline-pattern='^(INFO|DEBUG|ERROR|WARN|TRACE|FATAL)' \
--env LOG_LEVEL=debug \
--env HOST=127.0.0.1 \
--env PUBLIC_OR_PROXY_ENDPOINT=${EXPLORER_URI} \
--env GRAPH_CONNECTION_URL=${NEPTUNE_URI} \
--env USING_PROXY_SERVER=true \
--env IAM=${IAM} \
--env AWS_REGION=${AWS_REGION} \
--env SERVICE_TYPE=${SERVICE} \
--env PROXY_SERVER_HTTPS_CONNECTION=false \
--env NEPTUNE_NOTEBOOK=true public.ecr.aws/neptune/graph-explorer:${EXPLORER_ECR_TAG}
}

Expand All @@ -170,13 +164,10 @@ start_graph_explorer_with_default_logs() {
--restart always \
--env LOG_LEVEL=debug \
--env HOST=127.0.0.1 \
--env PUBLIC_OR_PROXY_ENDPOINT=${EXPLORER_URI} \
--env GRAPH_CONNECTION_URL=${NEPTUNE_URI} \
--env USING_PROXY_SERVER=true \
--env IAM=${IAM} \
--env AWS_REGION=${AWS_REGION} \
--env SERVICE_TYPE=${SERVICE} \
--env PROXY_SERVER_HTTPS_CONNECTION=false \
--env NEPTUNE_NOTEBOOK=true public.ecr.aws/neptune/graph-explorer:${EXPLORER_ECR_TAG}
}

Expand Down
2 changes: 1 addition & 1 deletion docs/guides/deploy-with-docker.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ You can find the latest version of the image on

> [!NOTE]
>
> Make sure to use the version of the image that does not include `sagemaker` in the tag.
> Both the regular and `sagemaker-*` image tags contain the same image. You can use either.

## Prerequisites

Expand Down
Loading
Loading