# How to Run an Ephemery Public Testnet Node with Besu and Teku
In this tutorial, I’m going to walk you through how I set up an Ephemery testnet node using Besu for the execution layer and Teku for the consensus layer. If you're curious about running your own Ethereum-like beacon chain locally or just want a clean test environment that resets every four weeks, Ephemery is a great choice. I'll show you exactly what I did to get it up and running. Not to mention that went through the process as a normal user. So there is no need for special skills in DevOps, you can do it!
>[!Note]
This is a beacon node only (without validator duties)!
- Prerequisites
Docker & Docker-Compose installed
## Step 1: Set Up Your Working Directory
Start by creating a project folder:
```bash
mkdir ephemery-node && cd ephemery-node
```
## Step 2: Download the Latest Ephemery Files
fetch the file and unpack it inside testnet-all folder
```bash
curl -L -o testnet-all.tar.gz https://github.com/ephemery-testnet/ephemery-genesis/releases/latest/download/testnet-all.tar.gz
mkdir testnet-all && tar -xzf testnet-all.tar.gz -C testnet-all
```
## Step 3: Load the environment variables
```bash
cd testnet-all
source nodevars_env.txt
cd ..
```
This will give you access to:
- `$BOOTNODE_ENODE` – for Besu
- `$BOOTNODE_ENR_LIST` – for Teku
You can run echo `$BOOTNODE_ENODE` to check the values if needed.
You should now have:
```bash
echo "$BOOTNODE_ENODE" # e.g. enode://…@IP:30303
echo "$BOOTNODE_ENR_LIST" # e.g. enr:-… ,enr:-…
echo "$GENESIS_SSZ_PATH" # genesis.ssz
echo "$CONFIG_YAML_PATH" # config.yaml
```
Create a .env file in the same directory as your docker-compose.yml and paste in the values from your nodevars_env.txt like this(values are real):
```env
BOOTNODE_ENODE=enode://50a54ecbd2175497640bcf46a25bbe9bb4fae51d7cc2a29ef4947a7ee17496cf39a699b7fe6b703ed0feb9dbaae7e44fc3827fcb7435ca9ac6de4daa4d983b3d@137.74.203.240:30303
BOOTNODE_ENR_LIST=enr:-LK4QM7kEZAgPNZmwuEdfS68qxtvXmdY0Uk4mSrflr3gDTFTNAS09cRc-eLE-Gb-1Mc6FqOJgphhAEFDc52Kil2HWOsEh2F0dG5ldHOIAAAAAMAAAACEZXRoMpDOicPBYAAQG___________gmlkgnY0gmlwhIlKy_CJc2VjcDI1NmsxoQNOcwdwpHPZTopFyzuvy2XT2k4Wy4gMbE7e7a1WHLlZo4N0Y3CCI4yDdWRwgiOM,enr:-Iq4QNMYHuJGbnXyBj6FPS2UkOQ-hnxT-mIdNMMr7evR9UYtLemaluorL6J10RoUG1V4iTPTEbl3huijSNs5_ssBWFiGAYhBNHOzgmlkgnY0gmlwhIlKy_CJc2VjcDI1NmsxoQNULnJBzD8Sakd9EufSXhM4rQTIkhKBBTmWVJUtLCp8KoN1ZHCCIyk,enr:-Jq4QKOark9WXFRsOLx4RGwdYrH_YDiK39KP0niQ6D5gagX4Jo7ZxuzPwRN6YWedFhvlWP305qoDq8X4AmQ2PyyW3ukBhGV0aDKQzonDwWAAEBv__________4JpZIJ2NIJpcIRBbZouiXNlY3AyNTZrMaEDSZOVyosoT9D64UUw4_XiFapjaeCsvT0G5Zsnf81gyymDdWRwgiMp,enr:-Iq4QIc297-de1P6hznMX2cIdVsQkve9BD9NUsJ7vVQa7eh5UpekA9rLid5A-yLiS3gZwOGugYZPi58x76zNs2cEQFCGAYhBJlTYgmlkgnY0gmlwhEFtmi6Jc2VjcDI1NmsxoQJDyix-IHa_mVwLBEN9NeG8I-RUjNQK_MGxk9OqRQUAtIN1ZHCCIyg,enr:-OS4QPmu9RBpnkIciCJQCpTCdkZDUdp1Zl259VcYGGz1KinVa9dAWRxC-dK6X8vlRR1kffpQwkeLMzDE_nDtG05mVVYDh2F0dG5ldHOIAAMAAAAAAACGY2xpZW502IpMaWdodGhvdXNljDcuMC4wLWJldGEuN4RldGgykM6Jw8FgABAb__________-CaWSCdjSCaXCEp-sBuYRxdWljgiNRiXNlY3AyNTZrMaECRFTZ7g8SwCvj7Gwa7OSjBb51_pd0v--6Tbto_k9R6qSIc3luY25ldHMAg3RjcIIjUIN1ZHCCI1A
```
## Step 4: Create a JWT Secret File
This is required for Besu and Teku to talk over the Engine API:
```bash
mkdir shared
openssl rand -hex 32 > shared/token.txt
```
## Step 5: Create docker-compose.yml
Create a file named docker-compose.yml with this content:
```yaml
---
version: "3.4"
services:
besu_node:
image: hyperledger/besu:develop
command:
[
"--network=ephemery",
"--bootnodes=${BOOTNODE_ENODE}",
"--rpc-http-enabled=true",
"--rpc-http-cors-origins=*",
"--rpc-ws-enabled=true",
"--host-allowlist=*",
"--engine-host-allowlist=*",
"--engine-rpc-enabled",
"--engine-jwt-secret=/shared/token.txt",
"--p2p-port=30303",
"--p2p-host=0.0.0.0",
]
volumes:
- ./besu:/var/lib/besu/data
- ./shared:/shared
ports:
# Map the p2p port(30303), RPC HTTP port(8545), and engine port (8551)
- "8545:8545"
- "8551:8551"
- "30303:30303/tcp"
- "30303:30303/udp"
teku_node:
environment:
- "JAVA_OPTS=-Xmx4g"
image: consensys/teku:develop
command:
[
"--network=https://ephemery.dev/latest/config.yaml",
"--ee-endpoint=http://besu_node:8551",
"--ee-jwt-secret-file=/shared/token.txt",
"--metrics-enabled=true",
"--rest-api-enabled=true",
"--ignore-weak-subjectivity-period-enabled=true",
"--checkpoint-sync-url=https://ephemery.beaconstate.ethstaker.cc/",
"--p2p-advertised-ip=127.0.0.1",
"--p2p-port=9000",
"--p2p-discovery-bootnodes=${BOOTNODE_ENR_LIST}",
]
depends_on:
- besu_node
volumes:
- ./teku:/var/lib/teku/data
- ./shared:/shared
ports:
# Map the p2p port(9000) and REST API port(5051)
- "9000:9000/tcp"
- "9000:9000/udp"
- "5051:5051"
```
## Step 6: Start Your Node
Back in the project root inside `ephemery-node` folder, run:
```bash
docker-compose up
```
Watch logs, you should see:
```Besu → “Starting synchronizer…”
Teku → “Execution Client is online”
Both → gradually “Connected peers: N > 0”
Teku → “Syncing *** Slot: … Head slot: … Remaining slots: …”
```
Screenshots from logs on my running node:


>[!Warning]
>I've got this :point_down: error in the screenshot below when entered `ephemery` as network for Teku command options. so you better enter the url as the value for network.

Running Ephemery is a bit tricky here, at least for now. hopefully soon in future I will check into Teku codebase see why this error arises.
>[!Tip]
Some tips if you got problem with running with above inputs:
- Running this command should show your head state and attestations.
```bash
curl -s localhost:5051/eth/v1/beacon/states/head/finality_checkpoints
```
- Check your bootnodes / ENR_LIST are correct.
- Ensure UDP/TCP ports (30303, 9000) are open in Docker and firewall.
- Try explicit host IP instead of 0.0.0.0 or 127.0.0.1 for advertised-ip.
- The valu for `engine-jwt-secret` should point at the same `/shared/token.txt`.
- If no peer, copy and paste the values for `--bootnodes` and `--p2p-discovery-bootnodes` from the testnet-all folder directly into the docker-compose file.
If you have any question I would be glad to help!
Happy testing!🥳