Skip to content

feat: create k8s deploy#6

Merged
webpiratt merged 4 commits intomainfrom
feat/deploy-k8s
Feb 27, 2026
Merged

feat: create k8s deploy#6
webpiratt merged 4 commits intomainfrom
feat/deploy-k8s

Conversation

@4others
Copy link
Contributor

@4others 4others commented Feb 25, 2026

Summary by CodeRabbit

  • New Features

    • Added a /healthz health-check endpoint
    • Added Kubernetes manifests (service, deployment, configmaps, ingress) with resource requests/limits and readiness/liveness probes
  • Chores & DevOps

    • Removed CoinGecko API key from configuration and example files
    • Removed legacy SSH deployment script
    • Added container build (multi-stage) and CI/CD workflows for dev and production builds/deploys

@4others 4others added the don't merge make sure all the secrets are ready label Feb 25, 2026
@4others 4others requested a review from webpiratt February 25, 2026 08:24
Copy link
Contributor

@webpiratt webpiratt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, please review comments

@coderabbitai
Copy link

coderabbitai bot commented Feb 26, 2026

Walkthrough

Replaces API-key based CoinGecko config with a proxy-only client, removes related env variable and deployment scripts, adds multi-stage Docker build and Kubernetes manifests, introduces GitHub Actions workflows for image build/push and dev/prod deployments, and adds a /healthz endpoint to the server.

Changes

Cohort / File(s) Summary
Env & Config
.env.example, internal/config/config.go, CLAUDE.md
Removed COINGECKO_API_KEY entry from example env and from Config; minor whitespace/formatting cleanup in CLAUDE.md.
CoinGecko Client
internal/coingecko/client.go, internal/coingecko/client_test.go
Switched client to use proxied base URL; removed apiKey field and changed constructor to NewClient(); tests updated to instantiate client without API key.
Server Code
cmd/mcp-server/main.go
Updated CoinGecko client initialization to NewClient() and added /healthz HTTP endpoint.
Docker Build
Dockerfile
Added multi-stage Dockerfile: Go builder with TARGETARCH-aware setup and DKLS include handling; runtime image based on Ubuntu 22.04.
Kubernetes Manifests
deploy/01_server.yaml, deploy/dev/01_config.yaml, deploy/dev/02_ingress.yaml, deploy/prod/01_config.yaml, deploy/prod/02_ingress.yaml
Added Service, Deployment (with probes, resources, env from ConfigMap), and ConfigMap/Ingress manifests for dev and prod environments.
GitHub Actions: New Workflows
.github/workflows/build-push-image.yaml, .github/workflows/deploy-dev.yaml, .github/workflows/deploy-prod.yaml, .github/workflows/sync-main-to-dev.yaml
Added reusable build/push workflow and pipelines to build/push images, deploy to dev on branch push, deploy to prod on release, and sync main→dev.
Legacy CI/Deploy Removed
.github/workflows/deploy.yml, scripts/deploy.sh
Deleted previous deploy workflow and SSH-based deploy script.
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: create k8s deploy' directly summarizes the main change: setting up Kubernetes deployment infrastructure with workflows, manifests, and configuration files.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/deploy-k8s

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@4others 4others removed the don't merge make sure all the secrets are ready label Feb 26, 2026
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/coingecko/client_test.go (1)

8-120: ⚠️ Potential issue | 🟠 Major

Gate these tests as integration tests to prevent CI flakiness.

These tests make unconditional network calls to live CoinGecko endpoints. They lack the skip mechanism already used in internal/blockchair/client_test.go. During CI, go test ./... runs without integration test environment variables, causing these tests to fail if the network is unavailable, rate-limited, or the API changes.

Apply the skipUnlessIntegration pattern already established in blockchair tests:

Suggested patch
 import (
 	"context"
+	"os"
 	"testing"
 )
 
+func skipUnlessIntegration(t *testing.T) {
+	t.Helper()
+	if os.Getenv("COINGECKO_TEST") != "1" {
+		t.Skip("set COINGECKO_TEST=1 to run CoinGecko integration tests")
+	}
+}
+
 func TestSearch(t *testing.T) {
+	skipUnlessIntegration(t)
 	c := NewClient()
@@
 func TestCoinDetail_Token(t *testing.T) {
+	skipUnlessIntegration(t)
 	c := NewClient()
@@
 func TestCoinDetail_NativeAsset(t *testing.T) {
+	skipUnlessIntegration(t)
 	c := NewClient()
@@
 func TestSearch_Cache(t *testing.T) {
+	skipUnlessIntegration(t)
 	c := NewClient()
@@
 func TestCoinDetail_Cache(t *testing.T) {
+	skipUnlessIntegration(t)
 	c := NewClient()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/coingecko/client_test.go` around lines 8 - 120, These tests perform
live CoinGecko network calls and should be gated as integration tests; add the
same skipUnlessIntegration(t) guard used in internal/blockchair tests at the top
of each test function (TestSearch, TestCoinDetail_Token,
TestCoinDetail_NativeAsset, TestSearch_Cache, TestCoinDetail_Cache) so they call
skipUnlessIntegration(t) before making any client calls to avoid CI flakiness
when integration tests are not enabled.
🧹 Nitpick comments (4)
Dockerfile (1)

5-5: Use --no-install-recommends for apt installs.

This reduces image size and unnecessary package surface.

Suggested patch
-RUN apt-get update && apt-get install -y clang lld wget
+RUN apt-get update && apt-get install -y --no-install-recommends clang lld wget && rm -rf /var/lib/apt/lists/*

-RUN apt-get update && apt-get install -y \
+RUN apt-get update && apt-get install -y --no-install-recommends \
     ca-certificates \
     && rm -rf /var/lib/apt/lists/*

Also applies to: 29-31

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Dockerfile` at line 5, Update the apt-get install RUN lines to include
--no-install-recommends to avoid pulling recommended packages; specifically
modify the RUN commands that call "apt-get install -y clang lld wget" (and the
other install blocks around lines 29-31) to add --no-install-recommends after
apt-get install and keep apt-get update && apt-get install -y ... && rm -rf
/var/lib/apt/lists/* pattern to minimize image size.
.github/workflows/sync-main-to-dev.yaml (1)

8-13: Add workflow concurrency to prevent push races.

Back-to-back pushes to main can overlap and both try to push dev, causing avoidable non-fast-forward failures.

Suggested patch
 name: Sync main to dev

 on:
   push:
     branches:
       - main
+
+concurrency:
+  group: sync-main-to-dev
+  cancel-in-progress: true
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/sync-main-to-dev.yaml around lines 8 - 13, The workflow
lacks a concurrency policy which allows overlapping runs of the sync-main-to-dev
job ("sync-main-to-dev") to race and cause non-fast-forward failures; add a
concurrency stanza to the workflow (at the workflow root or job level for the
"sync-main-to-dev" job) that defines a stable group name (e.g., based on repo
and branch like "sync-main-to-dev-${{ github.ref }}") and set
cancel-in-progress: true so only one push-run proceeds at a time and in-flight
runs are cancelled to prevent push races.
deploy/prod/01_config.yaml (1)

6-6: Consider RPC failover for production resilience.

Using one public Ethereum RPC endpoint creates a single upstream failure/rate-limit point. Consider managed RPC with SLA or a failover strategy.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@deploy/prod/01_config.yaml` at line 6, The production config currently
hardcodes a single RPC endpoint via the eth-rpc-url key which is a single point
of failure; change this to support managed RPC or a failover list by replacing
eth-rpc-url with a configurable list (e.g., eth-rpc-urls) or an env var that
accepts multiple endpoints and update the app code that reads eth-rpc-url to
iterate/failover with retries and backoff (or use a hosted RPC provider with
SLA) so the client code (where eth-rpc-url is consumed) will try the next
endpoint on error or rate-limit and log which endpoint failed.
deploy/01_server.yaml (1)

37-37: Avoid mutable :latest for deploy images.

At Line 37, :latest makes rollouts non-deterministic and harder to roll back. Prefer an immutable tag (or digest) produced by CI.

♻️ Example change
-          image: ghcr.io/vultisig/mcp/mcp-server:latest
+          image: ghcr.io/vultisig/mcp/mcp-server:${IMAGE_TAG}

Or pin directly by digest:

-          image: ghcr.io/vultisig/mcp/mcp-server:latest
+          image: ghcr.io/vultisig/mcp/mcp-server@sha256:<digest>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@deploy/01_server.yaml` at line 37, Replace the mutable image tag
"ghcr.io/vultisig/mcp/mcp-server:latest" with an immutable, CI-produced
identifier: either a build-specific tag variable (e.g., injected CI variable
like MCP_SERVER_TAG used in the Deployment spec) or the image digest
(ghcr.../mcp-server@sha256:<digest>); update the deployment template so the
image field in the Server Deployment (image: ghcr.io/vultisig/mcp/mcp-server) is
parameterized to accept the CI-generated tag/digest and ensure the CI pipeline
writes the exact tag/digest into the manifest or replaces the variable at deploy
time for deterministic rollouts and rollbacks.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/build-push-image.yaml:
- Line 75: The workflow currently disables build provenance with the setting
"provenance: false", which removes provenance attestations; change that setting
to enable provenance (set "provenance" to true or remove the explicit false so
the default enables it) in the build-push-image workflow to restore image
provenance attestations and improve supply-chain traceability.

In @.github/workflows/deploy-prod.yaml:
- Around line 31-37: The "Set GHCR packages public" workflow step is calling a
non-existent PATCH endpoint (gh api -X PATCH
"/orgs/${owner}/packages/container/${repo}%2Fmcp-server/visibility") which
silently fails; remove this step or replace it after researching a valid
approach for changing GHCR package visibility (e.g., manual UI change or an
official API/tool) and update the workflow accordingly—look for the step named
"Set GHCR packages public" and the gh api call string in the job to locate and
remove/replace it.

In `@deploy/01_server.yaml`:
- Around line 32-67: The pod currently has no securityContext; add a pod-level
securityContext (e.g., runAsNonRoot: true, runAsUser: 1000, runAsGroup: 1000,
fsGroup: 1000) and a container-level securityContext on the container named
"server" to harden runtime (set runAsNonRoot: true, allowPrivilegeEscalation:
false, privileged: false, readOnlyRootFilesystem: true, drop all capabilities,
and seccompProfile: { type: RuntimeDefault } or similar). Ensure these fields
are applied under the same spec that contains the "server" container so
readinessProbe/livenessProbe continue to work and resource requests/limits
remain unchanged. Validate the cluster supports the chosen UID/group and seccomp
settings before merging.
- Around line 43-47: The environment variable name used in the deployment
override is incorrect: replace the env var key ETH_RPC_URL with the
config-contract-compliant name EVM_ETHEREUM_URL so the app picks up the
ConfigMap value (keep the valueFrom configMapKeyRef key: eth-rpc-url unchanged);
update any other deployment or manifest occurrences of ETH_RPC_URL to
EVM_ETHEREUM_URL to ensure consistent config binding.

In `@Dockerfile`:
- Around line 27-37: Create and switch to a non-root runtime user in the
Dockerfile: add steps after copying artifacts to create a dedicated user/group
(e.g., appuser), chown the application files and the /usr/local/lib/dkls path
(referencing the COPY --from=builder /app/main and COPY --from=builder
/usr/local/lib/dkls lines and WORKDIR /app), and then set USER to that non-root
user before the final image is produced; keep the existing ENV LD_LIBRARY_PATH
line but ensure the non-root user has permission to read those libraries.
- Around line 7-13: The Dockerfile is fetching go-wrappers from
refs/heads/master.tar.gz which is mutable; change the wget URL to a pinned
immutable ref (a specific release tag or commit archive URL for go-wrappers) and
add a SHA256 checksum verification step before extracting: download the tarball
to a temp file, compute and compare its SHA256 against the expected value, fail
the build if it doesn't match, then extract and proceed (update the RUN command
that currently references master.tar.gz and the subsequent tar -xzf and cd
go-wrappers-master steps to use the pinned filename/dir). Ensure the checksum is
stored in the Dockerfile or ARG and the extraction path/name (e.g.,
go-wrappers-<tag>) is updated accordingly so the subsequent cp and
includes/linux-${TARGETARCH} checks still point to the correct directory.

---

Outside diff comments:
In `@internal/coingecko/client_test.go`:
- Around line 8-120: These tests perform live CoinGecko network calls and should
be gated as integration tests; add the same skipUnlessIntegration(t) guard used
in internal/blockchair tests at the top of each test function (TestSearch,
TestCoinDetail_Token, TestCoinDetail_NativeAsset, TestSearch_Cache,
TestCoinDetail_Cache) so they call skipUnlessIntegration(t) before making any
client calls to avoid CI flakiness when integration tests are not enabled.

---

Nitpick comments:
In @.github/workflows/sync-main-to-dev.yaml:
- Around line 8-13: The workflow lacks a concurrency policy which allows
overlapping runs of the sync-main-to-dev job ("sync-main-to-dev") to race and
cause non-fast-forward failures; add a concurrency stanza to the workflow (at
the workflow root or job level for the "sync-main-to-dev" job) that defines a
stable group name (e.g., based on repo and branch like "sync-main-to-dev-${{
github.ref }}") and set cancel-in-progress: true so only one push-run proceeds
at a time and in-flight runs are cancelled to prevent push races.

In `@deploy/01_server.yaml`:
- Line 37: Replace the mutable image tag
"ghcr.io/vultisig/mcp/mcp-server:latest" with an immutable, CI-produced
identifier: either a build-specific tag variable (e.g., injected CI variable
like MCP_SERVER_TAG used in the Deployment spec) or the image digest
(ghcr.../mcp-server@sha256:<digest>); update the deployment template so the
image field in the Server Deployment (image: ghcr.io/vultisig/mcp/mcp-server) is
parameterized to accept the CI-generated tag/digest and ensure the CI pipeline
writes the exact tag/digest into the manifest or replaces the variable at deploy
time for deterministic rollouts and rollbacks.

In `@deploy/prod/01_config.yaml`:
- Line 6: The production config currently hardcodes a single RPC endpoint via
the eth-rpc-url key which is a single point of failure; change this to support
managed RPC or a failover list by replacing eth-rpc-url with a configurable list
(e.g., eth-rpc-urls) or an env var that accepts multiple endpoints and update
the app code that reads eth-rpc-url to iterate/failover with retries and backoff
(or use a hosted RPC provider with SLA) so the client code (where eth-rpc-url is
consumed) will try the next endpoint on error or rate-limit and log which
endpoint failed.

In `@Dockerfile`:
- Line 5: Update the apt-get install RUN lines to include
--no-install-recommends to avoid pulling recommended packages; specifically
modify the RUN commands that call "apt-get install -y clang lld wget" (and the
other install blocks around lines 29-31) to add --no-install-recommends after
apt-get install and keep apt-get update && apt-get install -y ... && rm -rf
/var/lib/apt/lists/* pattern to minimize image size.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f4b81e0 and 5adb0ce.

📒 Files selected for processing (18)
  • .env.example
  • .github/workflows/build-push-image.yaml
  • .github/workflows/deploy-dev.yaml
  • .github/workflows/deploy-prod.yaml
  • .github/workflows/deploy.yml
  • .github/workflows/sync-main-to-dev.yaml
  • CLAUDE.md
  • Dockerfile
  • cmd/mcp-server/main.go
  • deploy/01_server.yaml
  • deploy/dev/01_config.yaml
  • deploy/dev/02_ingress.yaml
  • deploy/prod/01_config.yaml
  • deploy/prod/02_ingress.yaml
  • internal/coingecko/client.go
  • internal/coingecko/client_test.go
  • internal/config/config.go
  • scripts/deploy.sh
💤 Files with no reviewable changes (3)
  • .env.example
  • .github/workflows/deploy.yml
  • scripts/deploy.sh

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
.github/workflows/deploy-prod.yaml (1)

81-85: Optional cleanup: avoid re-mutating manifest in deploy job.

update-manifests already rewrites and pushes deploy/01_server.yaml; repeating sed here is redundant and can be removed to keep one source of truth for tag updates.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/deploy-prod.yaml around lines 81 - 85, Remove the
redundant tag-rewrite step in the deploy job that runs the shell snippet
updating deploy/01_server.yaml (the step named "Update deployment files" which
runs sed to replace mcp-server:${TAG}); since update-manifests already rewrites
and pushes deploy/01_server.yaml, delete this sed step (or the entire "Update
deployment files" step) so the manifest is only mutated once and the workflow
relies on update-manifests as the single source of truth.
.github/workflows/sync-main-to-dev.yaml (1)

19-26: Consider handling merge conflicts explicitly in the sync step.

If git merge origin/main conflicts, the job hard-fails. Consider adding explicit conflict handling/logging (or opening a PR fallback) to make the automation behavior clearer.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/sync-main-to-dev.yaml around lines 19 - 26, The sync step
named "Sync main to dev" currently hard-fails on merge conflicts; update the job
to detect the failure of the git merge command (the existing git merge
origin/main --no-edit) and handle it explicitly by logging the conflict,
aborting the merge (git merge --abort), creating a conflict branch (e.g.,
sync/main-into-dev-conflict-<SHA>), pushing that branch, and opening a PR
fallback (using the gh CLI or GitHub API) so conflicts can be resolved manually;
ensure the step returns a clear status/log message and only proceeds with git
push origin dev when the merge command succeeds.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@Dockerfile`:
- Line 25: The Dockerfile currently builds a single Go file (RUN go build -o
main cmd/${SERVICE}/main.go); change the build target to the package directory
so the package is compiled (e.g., use ./cmd/${SERVICE}/) — update the RUN
command that invokes go build in the Dockerfile to target ./cmd/${SERVICE}/
(keeping -o main and the SERVICE variable usage) to follow the
cmd/mcp-server/**/*.go guideline and avoid brittle single-file builds.

---

Nitpick comments:
In @.github/workflows/deploy-prod.yaml:
- Around line 81-85: Remove the redundant tag-rewrite step in the deploy job
that runs the shell snippet updating deploy/01_server.yaml (the step named
"Update deployment files" which runs sed to replace mcp-server:${TAG}); since
update-manifests already rewrites and pushes deploy/01_server.yaml, delete this
sed step (or the entire "Update deployment files" step) so the manifest is only
mutated once and the workflow relies on update-manifests as the single source of
truth.

In @.github/workflows/sync-main-to-dev.yaml:
- Around line 19-26: The sync step named "Sync main to dev" currently hard-fails
on merge conflicts; update the job to detect the failure of the git merge
command (the existing git merge origin/main --no-edit) and handle it explicitly
by logging the conflict, aborting the merge (git merge --abort), creating a
conflict branch (e.g., sync/main-into-dev-conflict-<SHA>), pushing that branch,
and opening a PR fallback (using the gh CLI or GitHub API) so conflicts can be
resolved manually; ensure the step returns a clear status/log message and only
proceeds with git push origin dev when the merge command succeeds.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5adb0ce and 7860f98.

📒 Files selected for processing (18)
  • .env.example
  • .github/workflows/build-push-image.yaml
  • .github/workflows/deploy-dev.yaml
  • .github/workflows/deploy-prod.yaml
  • .github/workflows/deploy.yml
  • .github/workflows/sync-main-to-dev.yaml
  • CLAUDE.md
  • Dockerfile
  • cmd/mcp-server/main.go
  • deploy/01_server.yaml
  • deploy/dev/01_config.yaml
  • deploy/dev/02_ingress.yaml
  • deploy/prod/01_config.yaml
  • deploy/prod/02_ingress.yaml
  • internal/coingecko/client.go
  • internal/coingecko/client_test.go
  • internal/config/config.go
  • scripts/deploy.sh
💤 Files with no reviewable changes (3)
  • .github/workflows/deploy.yml
  • scripts/deploy.sh
  • .env.example
🚧 Files skipped from review as they are similar to previous changes (7)
  • CLAUDE.md
  • internal/config/config.go
  • deploy/dev/01_config.yaml
  • deploy/dev/02_ingress.yaml
  • internal/coingecko/client_test.go
  • .github/workflows/deploy-dev.yaml
  • internal/coingecko/client.go

@webpiratt webpiratt merged commit ed1d8b6 into main Feb 27, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants