diff --git a/.github/workflows/release-relayer.yml b/.github/workflows/release-relayer.yml
index 7ea4a56521..2e5910de4c 100644
--- a/.github/workflows/release-relayer.yml
+++ b/.github/workflows/release-relayer.yml
@@ -7,6 +7,10 @@ on:
       - release-v1.0.0
   workflow_dispatch:
 
+env:
+  REGISTRY: ghcr.io
+  IMAGE_NAME: snowfork/snowbridge-relay
+
 jobs:
   release-relayer:
     runs-on: snowbridge-runner
@@ -19,12 +23,12 @@ jobs:
       - name: Set up Node.js
         uses: actions/setup-node@v2
         with:
-          node-version: '14'
+          node-version: "14"
 
       - name: Setup go
         uses: actions/checkout@v4
         with:
-          go-version: '^1.21.0'
+          go-version: "^1.21.0"
 
       - name: Install Go tools
         run: >
@@ -68,7 +72,7 @@ jobs:
           # Get the most recent tag in the format relayer-<branchname>-<version>
           current_version=$(git tag --list "relayer-${{ steps.branch_name.outputs.branch }}-*" --sort=-v:refname | head -n 1 | sed -E 's/relayer-${{ steps.branch_name.outputs.branch }}-//')
           echo "Current version: $current_version"
-          
+
           # If there is no current version, set it to 1.0.0
           if [ -z "$current_version" ]; then
           new_version="1.0.0"
@@ -117,3 +121,17 @@ jobs:
           asset_path: ./relayer/build/snowbridge-relay
           asset_name: snowbridge-relay
           asset_content_type: application/octet-stream
+
+      - name: Log in to the Container registry
+        uses: docker/login-action@v3
+        with:
+          registry: ${{ env.REGISTRY }}
+          username: ${{ github.actor }}
+          password: ${{ secrets.GITHUB_TOKEN }}
+
+      - name: Build and push Docker image
+        uses: docker/build-push-action@v6
+        with:
+          context: ./relayer
+          push: true
+          tags: ${{ steps.create_tag.outputs.tag }}
diff --git a/relayer/.dockerignore b/relayer/.dockerignore
index 722d5e71d9..6f27dc31ad 100644
--- a/relayer/.dockerignore
+++ b/relayer/.dockerignore
@@ -1 +1,2 @@
 .vscode
+.env
diff --git a/relayer/.env.example b/relayer/.env.example
new file mode 100644
index 0000000000..8084fb3478
--- /dev/null
+++ b/relayer/.env.example
@@ -0,0 +1,6 @@
+PARACHAIN_RELAY_ASSETHUB_ETH_KEY=
+EXECUTION_RELAY_ASSETHUB_SUB_KEY=
+CONFIG_DIR=/tmp/snowbridge
+AWS_ACCESS_KEY_ID=
+AWS_SECRET_ACCESS_KEY=
+AWS_REGION=eu-central-1
diff --git a/relayer/.gitignore b/relayer/.gitignore
index 10f3f2330f..2299552e47 100644
--- a/relayer/.gitignore
+++ b/relayer/.gitignore
@@ -2,3 +2,4 @@
 /.envrc
 build/
 .vscode/
+.env
diff --git a/relayer/Dockerfile b/relayer/Dockerfile
index bd36948d25..db94c72bad 100644
--- a/relayer/Dockerfile
+++ b/relayer/Dockerfile
@@ -1,8 +1,9 @@
-FROM golang:1.14
+FROM golang:1.21
 WORKDIR /opt/relayer
 ADD . .
 RUN go build -v -o build/snowbridge-relay main.go
 
-FROM parity/subkey:2.0.0
+FROM ubuntu:22.04
 COPY --from=0 /opt/relayer/build/snowbridge-relay /usr/local/bin/
+VOLUME ["/config"]
 ENTRYPOINT ["/usr/local/bin/snowbridge-relay"]
diff --git a/relayer/README.md b/relayer/README.md
index 32f6d88fbc..9616997262 100644
--- a/relayer/README.md
+++ b/relayer/README.md
@@ -55,6 +55,7 @@ Note: For local development and testing, we use our E2E test stack described [he
 
 For an example configuration, please consult the [setup script](https://github.com/Snowfork/snowbridge/blob/main/web/packages/test/scripts/start-services.sh) for our local development stack. Specifically the `start_relayer` bash function.
 
+
 ## Tests
 
 To run both unit and integration tests, start a local E2E test stack and run the following command:
@@ -62,3 +63,76 @@ To run both unit and integration tests, start a local E2E test stack and run the
 ```bash
 mage test
 ```
+
+## Running
+
+### Run message relayer with multiple instances
+
+Configuration required for different relayers to coordinate. Take `execution-relay` which relayes message from Ethereum to AssetHub for example, assuming there are 3 instances deployed:
+
+
+`execution-relay-asset-hub-0.json`
+
+```
+
+{
+  ...
+  "schedule": {
+    "id": 0,
+    "totalRelayerCount": 3,
+    "sleepInterval": 20
+  }
+}
+
+```
+
+`execution-relay-asset-hub-1.json`
+
+```
+
+{
+  ...
+  "schedule": {
+    "id": 1,
+    "totalRelayerCount": 3,
+    "sleepInterval": 20
+  }
+}
+
+```
+
+`execution-relay-asset-hub-2.json`
+
+```
+
+{
+  ...
+  "schedule": {
+    "id": 2,
+    "totalRelayerCount": 3,
+    "sleepInterval": 20
+  }
+}
+
+```
+
+- id: ID of current relayer(start from 0)
+- totalRelayerCount: Number of total count of all relayers
+- sleepInterval: Sleep interval(in seconds) to check if message(nonce) has already been relayed
+
+The configuration above applies also to multiple instances of `parachain-relay` which relayes message from AssetHub to Ethereum.
+
+
+### Cost Analysis
+
+#### Running Ethereum message relayer
+
+For Ethereum message relayer take [extrinsic](https://bridgehub-polkadot.subscan.io/extrinsic/3264574-2) for example:
+
+As we can see it will cost 0.041163771 DOT as transaction fee and the reward is 0.053783 DOT, so it's about 0.012 DOT as incentive for each message.
+
+#### Running Parachain message relayer
+
+For Parachain message relayer take [transaction]( https://dashboard.tenderly.co/snowfork/snowbridge-polkadot/tx/1/0x2dbcf28f8d80c43acd3f08e15b0ec2e3c2c8a929d50e0cba2e3bba5d39738bce) for example:
+
+As we can see it will cost 0.000628 ETH as transaction fee and the reward is 0.000942 ETH, so it's about 0.0003 ETH as incentive for each message.
diff --git a/relayer/docker-compose.yml b/relayer/docker-compose.yml
new file mode 100644
index 0000000000..13b653143c
--- /dev/null
+++ b/relayer/docker-compose.yml
@@ -0,0 +1,24 @@
+version: "3"
+
+services:
+  execution-assethub:
+    platform: linux/amd64
+    image: ghcr.io/snowfork/snowbridge-relay
+    build:
+      context: .
+    command: run execution --config /config/execution-relay-asset-hub-0.json --substrate.private-key ${EXECUTION_RELAY_ASSETHUB_SUB_KEY}
+    volumes:
+      - ${CONFIG_DIR}:/config
+    env_file:
+      - .env
+    restart: on-failure
+
+  parachain-assethub:
+    platform: linux/amd64
+    image: ghcr.io/snowfork/snowbridge-relay
+    command: run parachain --config /config/parachain-relay-asset-hub-0.json --ethereum.private-key ${PARACHAIN_RELAY_ASSETHUB_ETH_KEY}
+    volumes:
+      - ${CONFIG_DIR}:/config
+    env_file:
+      - .env
+    restart: on-failure
diff --git a/relayer/start-relayer-in-docker.sh b/relayer/start-relayer-in-docker.sh
new file mode 100755
index 0000000000..f6558cbcf0
--- /dev/null
+++ b/relayer/start-relayer-in-docker.sh
@@ -0,0 +1,26 @@
+#!/usr/bin/env bash
+set -eu
+
+source ../web/packages/test/scripts/set-env.sh
+
+start_relayer()
+{
+    docker compose up -d
+}
+
+stop_relayer()
+{
+    docker compose down
+}
+
+build_image()
+{
+    docker build -f Dockerfile -t snowbridge-relayer .
+}
+
+if [ -z "${from_start_services:-}" ]; then
+    echo "start relayers only!"
+    trap kill_all SIGINT SIGTERM EXIT
+    start_relayer
+    wait
+fi
diff --git a/web/packages/test/scripts/deploy-ethereum.sh b/web/packages/test/scripts/deploy-ethereum.sh
index e91846437f..b96ad21213 100755
--- a/web/packages/test/scripts/deploy-ethereum.sh
+++ b/web/packages/test/scripts/deploy-ethereum.sh
@@ -20,6 +20,7 @@ start_geth() {
             --miner.etherbase=0xBe68fC2d8249eb60bfCf0e71D5A0d2F2e292c4eD \
             --authrpc.addr="127.0.0.1" \
             --http.addr="0.0.0.0" \
+            --ws.addr="0.0.0.0" \
             --http.corsdomain '*' \
             --allow-insecure-unlock \
             --authrpc.jwtsecret config/jwtsecret \
@@ -58,6 +59,7 @@ start_lodestar() {
             --genesisTime $timestamp \
             --startValidators "0..7" \
             --enr.ip6 "127.0.0.1" \
+            --rest.address "0.0.0.0" \
             --eth1.providerUrls "http://127.0.0.1:8545" \
             --execution.urls "http://127.0.0.1:8551" \
             --dataDir "$ethereum_data_dir" \