inkonchain node: an op-geth Compose runbook with a May 31, 2026 expiry
How to run an Ink Node
At a glance
- What is it?
- This repository is a Docker Compose runbook for running an Ink node out of an op-geth execution client, forked from simple-optimism-node. It is honest about its own shelf life: the README says op-reth support is not shipped yet and lists the five pieces that still have to exist before the stack can outlive op-geth.
- Who is it for?
- Run this stack if you want an Ink node for testing and accept it as a current-state runbook rather than a long-lived deployment. Anyone operating a production node should read the op-geth deprecation notice first, because the repository has no op-reth Compose path, no archive handling for reth snapshots, and no tagged release since 2025-05-15 to fall back on.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 57 days ago.
- What is it written in?
- Mainly Shell, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 1, 2026, and from our analysis. They are not legal advice.
Editorial analysis
op-geth support ends May 31, 2026 and this stack still runs op-geth
The date in the first paragraph decides how long this repository is useful as written. It is a fork and customization of smartcontracts/simple-optimism-node, and it says plainly that the instructions are the current `op-geth` runbook for this Compose stack, not the long-term recommendation. Per Optimism, `op-geth` support ends on May 31, 2026, and a node still running it at the L1 Glamsterdam hardfork will not be able to follow the canonical chain. `op-node` is not being deprecated, so the rollup half of the stack stays valid while the execution half expires.
What is not shipped is an `op-reth` Compose path, and the missing pieces are enumerated rather than hidden: a validated `op-reth` service, image, and entrypoint in `docker-compose.yml`; an `op-node` engine endpoint that no longer points at `http://op-geth:8551`; archive init and snapshot handling that can consume `op-reth` snapshots; healthcheck and monitoring updates that still target `op-geth` and the `opgeth` InfluxDB database; and env and port naming such as `PORT__OP_GETH_*` and `envs/*/op-geth.env` that no longer assumes the old client. Consequence for a reader: this is a runbook with a maintenance clock, and the clock belongs to Optimism rather than to the repository. The project is not archived and its last push is dated 2026-08-05, yet the newest release is v2.0.0 from 2025-05-15, with v1.3.0 and v1.2.1 before it, so no tag matches the op-geth stack described in the runbook and pinning to a release hands you a tree more than a year older than the last commit.
Two geth volumes, and the init container is configured from op-geth.env
The Compose file is short enough to read end to end, and the wiring explains most of what the runbook does. `op-geth` runs the image `us-docker.pkg.dev/oplabs-tools-artifacts/images/op-geth:v1.101503.4` with the entrypoint `/scripts/start-op-geth.sh`, a `stop_grace_period` of `5m`, three env files, and the volumes `./scripts/` at `/scripts`, `shared` at `/shared`, and `op_geth` at `/geth`. `op-node` runs `op-node:v1.13.2` through `/scripts/start-op-node.sh` and mounts only `./scripts/` and `shared`, with no execution datadir volume of its own. `healthcheck` pulls `ethereumoptimism/replica-healthcheck` with a `latest` default on the `IMAGE_TAG__HEALTHCHECK` variable. Both long-running services declare `extra_hosts` mapping `host.docker.internal` to `host-gateway`, which is how a container reaches an RPC running on the host.
Two details carry the migration debt. Image tags are pinned to exact versions in the YAML rather than floating, so an execution client upgrade is a file edit plus a pull, not a flag. And `bedrock-init` is built from `./docker/dockerfiles/Dockerfile.bedrock-init`, runs `/scripts/init-bedrock.sh`, loads `./envs/${NETWORK_NAME}/op-geth.env` among its env files, and mounts both `op_geth:/geth` and a second volume `geth:/le`. The one-time init container is therefore named and configured after the execution client it initializes. Consequence: an `op-reth` move has to rewrite the init container's env inputs and its datadir volumes before anything starts, which is why the guidance treats that work as operator-owned rather than as a version bump.
NODE_TYPE=archive resolves a geth snapshot, and the documented fallback is to start over
`NODE_TYPE` is the first decision in `.env`, and the template recommends `full`:
NETWORK_NAME=ink-sepolia
NODE_TYPE=full
OP_NODE__RPC_ENDPOINT=<your Sepolia execution RPC>
OP_NODE__L1_BEACON=<your Sepolia beacon API>
OP_NODE__RPC_TYPE=basic
HEALTHCHECK__REFERENCE_RPC_PROVIDER=https://rpc-gel-sepolia.inkonchain.com`full` starts from an empty local datadir and is called the validated first-run path. `archive` is the alternative: it resolves the newest archival geth datadir for the current `op-geth` stack from the Gelato ChainSnap index for your network, downloads the matching `.sha256`, verifies the archive, and extracts it during `bedrock-init`. The template warns that `archive` needs much more disk and that if the snapshot lookup fails you should switch back to `full`. That fallback is the honest reading of the mechanism, because a failed snapshot resolution leaves you beginning a full sync rather than retrying the download.
The limitation is structural. Every noun in that description is geth: a geth datadir, a geth snapshot pointer, the current `op-geth` stack. The checked Sepolia Ink Gelato index already exposes `reth/full/datadir` artifacts while the checked mainnet Ink index still only exposes geth archives, and the repository uses neither. Consequence: an operator who needs archive history on `ink-mainnet` would have nothing to consume even if an op-reth path appeared, and the `full` path is the only route this repository supports today. Choosing `archive` also commits you to the 2 TB disk figure rather than the 500 GB one.
PORT__OP_NODE_P2P renames the host mapping while the container keeps listening on 9003
Ports are declared with a `PORT__` prefix and each one carries a default inside `docker-compose.yml`, which is convenient until you change one. `PORT__OP_NODE_P2P` republishes the host side of the op-node P2P listener on both udp and tcp, and the in-container `op-node` listener still uses `9003` whatever you set, so the variable renames the mapping without touching the process. The execution client publishes `${PORT__OP_GETH_HTTP:-9993}` to container port `8545` and `${PORT__OP_GETH_WS:-9994}` to `8546`, the rollup node publishes `${PORT__OP_NODE_HTTP:-9545}` to `9545`, and the healthcheck sidecar publishes `${PORT__HEALTHCHECK_METRICS:-7300}` to `7300`.
That arrangement buys you a predictable host port set to firewall, since 9993, 9994, 9003, 9545, and 7300 are the ports the smoke tests and dashboards expect. What it does not buy is a port scheme that survives the client rename, because the variable names themselves encode the old execution client in `PORT__OP_GETH_HTTP`, `PORT__OP_GETH_WS`, and `PORT__OP_GETH_P2P`. The naming is listed as one of the items an op-reth path still needs. Consequence: anything you script around those names becomes something to edit later, and if you change the P2P host port by hand you have changed a mapping rather than a listener, so the container-side number stays where it was. Set the variable when you need a different host port and leave the defaults alone when you do not.
OVERRIDE_HOLOCENE and EXTENDED_ARG get appended to two different programs
`.env.example` opens with a REQUIRED block and then carries a set of advanced wrapper inputs that the guidance handles with unusual care. `OVERRIDE_HOLOCENE` and `EXTENDED_ARG` are the names it names as examples, with the instruction to leave them empty unless you know the flag is compatible with the process you want to change, because the shell entrypoints append them to both `op-geth` and `op-node`. That is the sharp edge in this repository: one pair of variables, appended to two programs with two different flag parsers, with nothing in between to reconcile them. The documentation does not say which service fails first when a value is valid for only one of the two.
The rest of the configuration is more forgiving. `OP_NODE__RPC_TYPE=basic` is described as the right default for generic providers, with `alchemy`, `quicknode`, and `erigon` reserved for providers that require them. `.env` overrides the same variables for every service that loads it in `docker-compose.yml`, which is named as `op-geth`, `op-node`, `healthcheck`, and `bedrock-init`, and `envs/<network>/op-node.env` already supplies the network P2P defaults, so most first-time setups need only the `.env` values. The template offers two public Sepolia endpoints, `https://ethereum-sepolia-rpc.publicnode.com` and `https://ethereum-sepolia-beacon-api.publicnode.com`, that it says worked during docs validation, and advises a provider with higher rate limits for a long-running node. Consequence: leave the wrapper inputs empty, and treat any flag experiment as something to roll back across both services at once.
The healthcheck sidecar is pinned to linux/amd64 on every host
The healthcheck service is the only one in the Compose file carrying an explicit platform line, and it is there for a stated reason:
healthcheck:
image: ethereumoptimism/replica-healthcheck:${IMAGE_TAG__HEALTHCHECK:-latest}
platform: linux/amd64
restart: unless-stopped
env_file:
- ./envs/common/healthcheck.env
- ./envs/${NETWORK_NAME}/healthcheck.env
- .env
ports:
- ${PORT__HEALTHCHECK_METRICS:-7300}:7300On Apple Silicon this sidecar runs as `linux/amd64`, Docker Desktop handles it automatically, and the first startup can take longer. Automatic handling is not the same as native execution: the pin is unconditional in the file, so on an arm64 Mac the metrics sidecar runs under emulation while `op-geth` and `op-node` carry no platform line at all. The service also loads three env files, one of them common to every network, so the same sidecar configuration serves `ink-sepolia` and `ink-mainnet` apart from the network-specific overrides. Consequence for an arm64 operator: the longest wait on a first start is the healthcheck container pulling an amd64 image rather than the chain sync, and a slow first boot is not evidence of a broken node. The same service is also part of the cleanup still owed to op-reth, since healthcheck and monitoring continue to target `op-geth` and the `opgeth` InfluxDB database, so dashboards built on those series need attention whenever the client changes.
One startup gate, four log lines, and two RPC calls that tell you nothing about sync
Startup has a single gate and a handful of log lines that tell you whether the gate opened. `op-geth` and `op-node` both wait for `bedrock-init` to create `/shared/initialized.txt`, and the advice for a stack that looks stuck is to check `bedrock-init` first. That container is a one-time init, so it will usually disappear from default `docker compose ps` output once it exits, which is why validation asks you to run `docker compose ps -a` and confirm that `bedrock-init` exited with code `0`. A fresh `full` node then syncs from an empty datadir, and during that window the two long-running services are waiting rather than failing.
The log lines are specific enough to match on:
docker compose logs --tail 50 bedrock-init op-geth op-nodeOn first boot `bedrock-init` prints `Creating JWT...` and `Creating Bedrock flag...`; on a restart with existing volumes it prints `Bedrock node already initialized`. The execution client reports `HTTP server started` and the rollup node reports `Rollup node started`. After those, the execution surface can be probed with a posted `eth_chainId` call:
curl -fsS -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"eth_chainId","params":[],"id":1}' http://127.0.0.1:9993The rollup node answers `rpc_modules` on `http://127.0.0.1:9545`, where a healthy reply on `ink-sepolia` includes the `optimism`, `opp2p`, and `health` modules. Consequence: a zero exit code from the container that vanished, plus those module names, is the closest thing here to a pass signal, and neither one reports sync progress.
Mainnet asks for four times the disk of testnet, and the Ubuntu block ends mid-command
The hardware table is the only place the repository quantifies anything, and it draws a hard line between the two networks. `ink-mainnet` asks for 16GB+ RAM, a 2 TB SSD with NVME recommended, and 100 Mbps+ download. `ink-sepolia` asks for the same 16GB+ RAM and the same 100 Mbps+ but only a 500 GB SSD, also with NVME recommended. That four-fold difference is what turns `NODE_TYPE=archive` into a mainnet decision rather than a free option.
Prerequisites are Docker Engine with Docker Compose v2 on Linux, or Docker Desktop on macOS and Windows, plus working L1 execution RPC and L1 beacon API endpoints for the Ethereum network that matches your target Ink network, plus enough free disk for the node type you chose. On Ubuntu the setup installs Docker from download.docker.com, adding a keyring and an apt source before the packages:
sudo apt-get update
sudo apt-get upgrade -y
sudo apt-get install -y curl gnupg ca-certificates lsb-release
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
sudo usermod -aG docker $(The snippet stops at `sudo usermod -aG docker $(`, which is the group-membership step described in the note above it, to be followed by logging out and back in and verifying with `docker ps`. Consequence: take the membership step from that note rather than from the truncated line, or the `docker` group membership never takes effect even though every package installed cleanly.
Editorial conclusion
Run this stack if you want an Ink node for testing and accept it as a current-state runbook rather than a long-lived deployment. Anyone operating a production node should read the op-geth deprecation notice first, because the repository has no op-reth Compose path, no archive handling for reth snapshots, and no tagged release since 2025-05-15 to fall back on.
Frequently asked questions
Which crypto nodes are the most profitable?
This repository says nothing about profitability or rewards. It specifies hardware of 16GB+ RAM, a 2 TB SSD for ink-mainnet or 500 GB for ink-sepolia, and 100 Mbps+ download, and it warns that a node still on op-geth at the Glamsterdam hardfork will not follow the canonical chain.
What does a crypto node do?
In this repository a node is a Compose stack: an op-geth execution client, an op-node rollup node, a healthcheck sidecar, prometheus, grafana, and influxdb, fronted by a one-time bedrock-init container that creates a JWT and the Bedrock flag. op-geth and op-node wait for bedrock-init to create /shared/initialized.txt before they start.
How many Ethereum to run a node?
The repository does not price a node in ETH. It asks for your own L1 execution RPC and L1 beacon API endpoints for the Ethereum network matching your target Ink network, and it names two public Sepolia endpoints that worked during docs validation while advising a higher-limit provider for a long-running node.
Do Kraken have INK?
Nothing in this repository addresses exchanges or listings, so it cannot answer that. Its scope is running an Ink node locally, with NETWORK_NAME set to either ink-sepolia or ink-mainnet and the healthcheck reference RPC pointing at rpc-gel-sepolia.inkonchain.com or rpc-gel.inkonchain.com.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/inkonchain-node)