Skip to content

Commit 9ac4470

Browse files
paulbalajiclaude
andauthored
feat: add standalone ccip-server Docker image and workflow (#7565)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 20d5989 commit 9ac4470

10 files changed

Lines changed: 250 additions & 15 deletions

File tree

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
name: Build and Push CCIP Server Image to GCR
2+
on:
3+
push:
4+
branches: [main]
5+
tags:
6+
- '**'
7+
pull_request:
8+
paths:
9+
- 'typescript/ccip-server/**'
10+
- '.github/workflows/ccip-server-docker.yml'
11+
workflow_dispatch:
12+
inputs:
13+
include_arm64:
14+
description: 'Include arm64 in the build'
15+
required: false
16+
default: 'false'
17+
18+
concurrency:
19+
group: build-push-ccip-server-${{ github.ref }}
20+
cancel-in-progress: true
21+
22+
jobs:
23+
check-env:
24+
runs-on: ubuntu-latest
25+
outputs:
26+
gcloud-service-key: ${{ steps.gcloud-service-key.outputs.defined }}
27+
steps:
28+
- id: gcloud-service-key
29+
env:
30+
GCLOUD_SERVICE_KEY: ${{ secrets.GCLOUD_SERVICE_KEY }}
31+
if: "${{ env.GCLOUD_SERVICE_KEY != '' }}"
32+
run: echo "defined=true" >> $GITHUB_OUTPUT
33+
34+
build-and-push-to-gcr:
35+
runs-on: ubuntu-latest
36+
permissions:
37+
contents: read
38+
id-token: write
39+
pull-requests: write
40+
41+
needs: [check-env]
42+
if: needs.check-env.outputs.gcloud-service-key == 'true'
43+
44+
steps:
45+
- name: Generate GitHub App Token
46+
id: generate-token
47+
uses: actions/create-github-app-token@v2
48+
with:
49+
app-id: ${{ secrets.HYPER_GONK_APP_ID }}
50+
private-key: ${{ secrets.HYPER_GONK_PRIVATE_KEY }}
51+
52+
- uses: actions/checkout@v5
53+
with:
54+
ref: ${{ github.event.pull_request.head.sha || github.sha }}
55+
submodules: recursive
56+
fetch-depth: 0
57+
58+
- name: Generate tag data
59+
id: taggen
60+
run: |
61+
echo "TAG_DATE=$(date +'%Y%m%d-%H%M%S')" >> $GITHUB_OUTPUT
62+
echo "TAG_SHA=$(echo '${{ github.event.pull_request.head.sha || github.sha }}' | cut -b 1-7)" >> $GITHUB_OUTPUT
63+
64+
- name: Docker meta
65+
id: meta
66+
uses: docker/metadata-action@v5
67+
with:
68+
images: |
69+
gcr.io/abacus-labs-dev/hyperlane-offchain-lookup-server
70+
tags: |
71+
type=ref,event=branch
72+
type=ref,event=pr
73+
type=raw,value=${{ steps.taggen.outputs.TAG_SHA }}-${{ steps.taggen.outputs.TAG_DATE }}
74+
75+
- name: Set up Depot CLI
76+
uses: depot/setup-action@v1
77+
78+
- name: Login to GCR
79+
uses: docker/login-action@v3
80+
with:
81+
registry: gcr.io
82+
username: _json_key
83+
password: ${{ secrets.GCLOUD_SERVICE_KEY }}
84+
85+
- name: Determine platforms
86+
id: determine-platforms
87+
run: |
88+
if [ "${{ github.event.inputs.include_arm64 }}" == "true" ]; then
89+
echo "platforms=linux/amd64,linux/arm64" >> $GITHUB_OUTPUT
90+
else
91+
echo "platforms=linux/amd64" >> $GITHUB_OUTPUT
92+
fi
93+
94+
- name: Get Foundry version
95+
id: foundry-version
96+
run: |
97+
FOUNDRY_VERSION=$(cat solidity/.foundryrc)
98+
echo "FOUNDRY_VERSION=$FOUNDRY_VERSION" >> $GITHUB_OUTPUT
99+
100+
- name: Build and push
101+
id: build
102+
uses: depot/build-push-action@v1
103+
with:
104+
project: 3cpjhx94qv
105+
context: ./
106+
file: ./typescript/ccip-server/Dockerfile
107+
push: true
108+
tags: ${{ steps.meta.outputs.tags }}
109+
labels: ${{ steps.meta.outputs.labels }}
110+
platforms: ${{ steps.determine-platforms.outputs.platforms }}
111+
build-args: |
112+
FOUNDRY_VERSION=${{ steps.foundry-version.outputs.FOUNDRY_VERSION }}
113+
114+
- name: Comment image tags on PR
115+
if: github.event_name == 'pull_request'
116+
uses: ./.github/actions/docker-image-comment
117+
with:
118+
comment_tag: ccip-server-docker-image
119+
image_name: CCIP Server Docker Image
120+
emoji: 🔍
121+
image_tags: ${{ steps.meta.outputs.tags }}
122+
pr_number: ${{ github.event.pull_request.number }}
123+
github_token: ${{ steps.generate-token.outputs.token }}
124+
job_status: ${{ job.status }}

Dockerfile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ COPY solidity/package.json ./solidity/
4242
COPY solhint-plugin/package.json ./solhint-plugin/
4343
COPY starknet/package.json ./starknet/
4444

45+
# Set dummy DATABASE_URL for ccip-server prisma generate during install
46+
ENV DATABASE_URL="postgresql://placeholder:placeholder@localhost:5432/placeholder"
47+
4548
RUN pnpm install --frozen-lockfile && pnpm store prune
4649

4750
# Copy everything else

turbo.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@
2626
"coverage": {
2727
"dependsOn": ["build"],
2828
"outputs": ["coverage/**", "fixtures/**"]
29+
},
30+
"bundle": {
31+
"dependsOn": ["^build"],
32+
"outputs": ["*-bundle/**"]
2933
}
3034
}
3135
}

typescript/ccip-server/Dockerfile

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
FROM node:20-slim AS builder
2+
3+
WORKDIR /hyperlane-monorepo
4+
5+
RUN apt-get update && apt-get install -y --no-install-recommends \
6+
git g++ make python3 python3-pip jq bash curl ca-certificates unzip \
7+
&& rm -rf /var/lib/apt/lists/*
8+
9+
# Install Foundry (Linux binaries) - pinned version for reproducibility
10+
ARG FOUNDRY_VERSION
11+
ARG TARGETARCH
12+
SHELL ["/bin/bash", "-c"]
13+
RUN set -o pipefail && \
14+
ARCH=$([ "$TARGETARCH" = "arm64" ] && echo "arm64" || echo "amd64") && \
15+
curl --fail -L "https://github.com/foundry-rs/foundry/releases/download/${FOUNDRY_VERSION}/foundry_${FOUNDRY_VERSION}_linux_${ARCH}.tar.gz" | tar -xzC /usr/local/bin forge cast
16+
SHELL ["/bin/sh", "-c"]
17+
18+
# Copy package.json first for corepack to read packageManager field
19+
COPY package.json ./
20+
RUN corepack enable && corepack install
21+
22+
# Copy pnpm config files
23+
COPY pnpm-lock.yaml pnpm-workspace.yaml ./
24+
25+
# Copy patches directory (required for pnpm install)
26+
COPY patches ./patches
27+
28+
# Copy only the packages needed for ccip-server
29+
COPY typescript/ccip-server/package.json ./typescript/ccip-server/
30+
COPY typescript/deploy-sdk/package.json ./typescript/deploy-sdk/
31+
COPY typescript/sdk/package.json ./typescript/sdk/
32+
COPY typescript/provider-sdk/package.json ./typescript/provider-sdk/
33+
COPY typescript/utils/package.json ./typescript/utils/
34+
COPY typescript/cosmos-sdk/package.json ./typescript/cosmos-sdk/
35+
COPY typescript/cosmos-types/package.json ./typescript/cosmos-types/
36+
COPY typescript/radix-sdk/package.json ./typescript/radix-sdk/
37+
COPY typescript/tsconfig/package.json ./typescript/tsconfig/
38+
COPY typescript/eslint-config/package.json ./typescript/eslint-config/
39+
COPY solidity/package.json ./solidity/
40+
COPY solhint-plugin/package.json ./solhint-plugin/
41+
COPY starknet/package.json ./starknet/
42+
43+
# Copy prisma schema before install (needed for postinstall prisma generate)
44+
COPY typescript/ccip-server/prisma ./typescript/ccip-server/prisma
45+
46+
# Set dummy DATABASE_URL for prisma generate during install (actual URL provided at runtime)
47+
ENV DATABASE_URL="postgresql://placeholder:placeholder@localhost:5432/placeholder"
48+
49+
RUN pnpm install --frozen-lockfile
50+
51+
# Run prisma generate after install (needed to generate Prisma client)
52+
RUN pnpm --filter @hyperlane-xyz/ccip-server prisma generate
53+
54+
# Copy source files
55+
COPY turbo.json ./
56+
COPY typescript/ccip-server ./typescript/ccip-server
57+
COPY typescript/deploy-sdk ./typescript/deploy-sdk
58+
COPY typescript/sdk ./typescript/sdk
59+
COPY typescript/provider-sdk ./typescript/provider-sdk
60+
COPY typescript/utils ./typescript/utils
61+
COPY typescript/cosmos-sdk ./typescript/cosmos-sdk
62+
COPY typescript/cosmos-types ./typescript/cosmos-types
63+
COPY typescript/radix-sdk ./typescript/radix-sdk
64+
COPY typescript/tsconfig ./typescript/tsconfig
65+
COPY typescript/eslint-config ./typescript/eslint-config
66+
COPY solidity ./solidity
67+
COPY solhint-plugin ./solhint-plugin
68+
COPY starknet ./starknet
69+
70+
# Build the ccip-server
71+
RUN pnpm turbo run build --filter=@hyperlane-xyz/ccip-server
72+
73+
# Create standalone deployment with resolved dependencies (no symlinks)
74+
# --legacy flag required for pnpm v10+ without inject-workspace-packages
75+
RUN pnpm --filter @hyperlane-xyz/ccip-server deploy --legacy --prod /app
76+
77+
# Copy generated Prisma client to dist (TypeScript doesn't copy non-TS files)
78+
# Note: Prisma outputs to src/generated/prisma/ (custom path), not node_modules/.prisma
79+
RUN cp -r /app/src/generated /app/dist/generated
80+
81+
# Production stage - Debian slim for Prisma native binary compatibility
82+
FROM node:20-slim AS runner
83+
84+
WORKDIR /app
85+
86+
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates \
87+
&& rm -rf /var/lib/apt/lists/*
88+
89+
# Copy the deployed standalone package
90+
COPY --from=builder /app ./
91+
92+
# Copy prisma schema for migrations
93+
COPY --from=builder /hyperlane-monorepo/typescript/ccip-server/prisma ./prisma
94+
95+
# Environment variables
96+
ENV NODE_ENV=production
97+
ENV LOG_LEVEL=info
98+
ENV SERVER_PORT=3000
99+
100+
# Expose ports
101+
EXPOSE 3000
102+
EXPOSE 9090
103+
104+
# Run the ccip-server
105+
CMD ["node", "dist/server.js"]

typescript/ccip-server/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
"typedocMain": "src/index.ts",
77
"private": true,
88
"files": [
9-
"src"
9+
"src",
10+
"dist"
1011
],
1112
"engines": {
1213
"node": ">=16"

typescript/ccip-server/prisma/schema.prisma

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ generator client {
66
output = "../src/generated/prisma"
77
}
88

9+
// DATABASE_URL is required for prisma generate but actual connection is handled in code
910
datasource db {
1011
provider = "postgresql"
1112
url = env("DATABASE_URL")

typescript/infra/helm/offchain-lookup-server/templates/deployment.yaml

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
# Container Deployment
2-
# The offchain-lookup typescript server, started with pnpm.
3-
# It is used for the ccip-read ISM
4-
# And can expose multiple endpoints depending on its ENV.
2+
# The offchain-lookup typescript server (CCIP-read ISM)
3+
# Can expose multiple endpoints depending on its ENV.
54
# The server code can be found in /typescript/ccip-server
65
apiVersion: apps/v1
76
kind: Deployment
@@ -32,8 +31,6 @@ spec:
3231
initialDelaySeconds: 5
3332
periodSeconds: 10
3433
imagePullPolicy: IfNotPresent
35-
command: ["pnpm"]
36-
args: ["-C", "typescript/ccip-server", "run", "start"]
3734
ports:
3835
- name: http
3936
containerPort: {{ .Values.port }}

typescript/infra/helm/offchain-lookup-server/values-mainnet.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ environment: mainnet
22

33
# General deployment configuration
44
image:
5-
repository: gcr.io/abacus-labs-dev/hyperlane-monorepo
5+
repository: gcr.io/abacus-labs-dev/hyperlane-offchain-lookup-server
66
# Modify this tag to deploy a new revision.
77
# Images can be found here:
8-
# https://console.cloud.google.com/artifacts/docker/abacus-labs-dev/us/gcr.io/hyperlane-monorepo?inv=1&invt=AbxRMg&project=abacus-labs-dev
9-
tag: 8da6852-20251215-172511
8+
# https://console.cloud.google.com/artifacts/docker/abacus-labs-dev/us/gcr.io/hyperlane-offchain-lookup-server
9+
tag: 8a41886-20251222-170941
1010

1111
# In Google Cloud Secret Manager, all secrets need to have a certain prefix in order to be accessible by
1212
# the Cluster Secret Store. For testnet this prefix is "hyperlane-testnet4"

typescript/infra/helm/offchain-lookup-server/values-testnet.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ environment: testnet
22

33
# General deployment configuration
44
image:
5-
repository: gcr.io/abacus-labs-dev/hyperlane-monorepo
5+
repository: gcr.io/abacus-labs-dev/hyperlane-offchain-lookup-server
66
# Modify this tag to deploy a new revision.
77
# Images can be found here:
8-
# https://console.cloud.google.com/artifacts/docker/abacus-labs-dev/us/gcr.io/hyperlane-monorepo?inv=1&invt=AbxRMg&project=abacus-labs-dev
9-
tag: 8da6852-20251215-172511
8+
# https://console.cloud.google.com/artifacts/docker/abacus-labs-dev/us/gcr.io/hyperlane-offchain-lookup-server
9+
tag: 8a41886-20251222-170941
1010

1111
# In Google Cloud Secret Manager, all secrets need to have a certain prefix in order to be accessible by
1212
# the Cluster Secret Store. For testnet this prefix is "hyperlane-testnet4"

typescript/infra/helm/offchain-lookup-server/values.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ environment: testnet
22

33
# General deployment configuration
44
image:
5-
repository: gcr.io/abacus-labs-dev/hyperlane-monorepo
5+
repository: gcr.io/abacus-labs-dev/hyperlane-offchain-lookup-server
66
# Modify this tag to deploy a new revision.
77
# Images can be found here:
8-
# https://console.cloud.google.com/artifacts/docker/abacus-labs-dev/us/gcr.io/hyperlane-monorepo?inv=1&invt=AbxRMg&project=abacus-labs-dev
9-
tag: 8da6852-20251215-172511
8+
# https://console.cloud.google.com/artifacts/docker/abacus-labs-dev/us/gcr.io/hyperlane-offchain-lookup-server
9+
tag: 8a41886-20251222-170941
1010

1111
secrets:
1212
name: 'offchain-lookup-server'

0 commit comments

Comments
 (0)