This guide walks you through setting up the complete CI/CD pipeline for the Healthy-Stellar contracts repository.
- Repository admin access
- Stellar testnet and/or mainnet account with XLM for deployments
- Basic understanding of GitHub Actions
Which secret does which workflow use? Use this table as the authoritative reference. If the table and any other section of this document disagree, the table wins — and please open a PR to fix the discrepancy.
| Secret Name | Workflow(s) | Purpose |
|---|---|---|
STELLAR_SECRET_KEY |
deploy-testnet.yml |
Raw secret key (S...) imported as the deployer identity for testnet deployments |
TESTNET_DEPLOYER_IDENTITY |
extend-ttls.yml |
Stellar identity credential used by the stellar CLI to extend contract TTLs on testnet |
MAINNET_DEPLOYER_IDENTITY |
extend-ttls.yml |
Stellar identity credential used by the stellar CLI to extend contract TTLs on mainnet |
ci.yml requires no secrets — it only runs cargo fmt, cargo clippy, cargo test, and a WASM build.
- Navigate to your repository on GitHub
- Go to Settings → Secrets and variables → Actions
- Click New repository secret
- Add each secret described below
- Name:
STELLAR_SECRET_KEY - Value: Your Stellar testnet secret key (starts with
S...) - Used by:
deploy-testnet.yml - Purpose: Imported as the
deployeridentity that signs testnet contract deployments
How to create a testnet deployer account:
# Install the Stellar CLI
cargo install --locked stellar-cli --features opt
# Generate a new identity (writes keys to ~/.config/stellar/identity/)
stellar keys generate deployer --network testnet
# Show the secret key to copy into the GitHub secret
stellar keys show deployer
# Get the public address (to fund the account)
stellar keys address deployerThen fund the account at the Stellar Laboratory Friendbot by pasting the public address.
⚠️ Security: Never commit secret keys to the repository or share them publicly. Rotate them if they are ever exposed.
- Name:
TESTNET_DEPLOYER_IDENTITY - Used by:
extend-ttls.yml - Purpose: Passed directly to
extend-ttls.shas the--identityargument and exported asSTELLAR_IDENTITYso thestellarCLI can sign TTL-extension transactions on testnet
How to obtain the value:
# After generating your testnet identity (see above), export it
stellar keys show deployer
# Copy the output — this is the value for TESTNET_DEPLOYER_IDENTITY- Name:
MAINNET_DEPLOYER_IDENTITY - Used by:
extend-ttls.yml(scheduled runs and manual mainnet triggers) - Purpose: Passed to
extend-ttls.shand exported asSTELLAR_IDENTITYso thestellarCLI can sign TTL-extension transactions on mainnet
How to obtain the value:
# Generate a separate mainnet identity — never reuse testnet keys on mainnet
stellar keys generate mainnet-deployer
# Show the secret key for the GitHub secret value
stellar keys show mainnet-deployer
# Get the address to fund with real XLM
stellar keys address mainnet-deployer
⚠️ Mainnet keys carry real financial risk. Use a dedicated account with the minimum XLM needed for TTL extensions. Rotate this secret immediately if it is ever exposed.
- Go to Settings → Actions → General
- Under "Actions permissions", select:
- ✅ Allow all actions and reusable workflows
- Under "Workflow permissions", select:
- ✅ Read and write permissions
- ✅ Allow GitHub Actions to create and approve pull requests
- Click Save
Protect the main branch to enforce CI requirements:
- Go to Settings → Branches
- Click Add rule (or edit existing rule for
main) - Configure the following:
main
- ✅ Enable
- Required approvals: 1
- ✅ Dismiss stale pull request approvals when new commits are pushed
- ✅ Require review from Code Owners (if you have a CODEOWNERS file)
- ✅ Enable
- ✅ Require branches to be up to date before merging
- Required status checks (these are the job names defined in
ci.yml):CI SuccessFormat CheckClippy LintTest SuiteBuild WASM
- ✅ Enable
- ⬜ Optional (recommended for enhanced security)
- ✅ Enable (keeps git history clean)
- ✅ Enable (even for administrators)
- Click Create or Save changes
The CI workflow runs on every push and pull request to main. It has four jobs — Format Check, Clippy Lint, Test Suite, and Build WASM — plus a gating CI Success job. No secrets are required.
- Create a test branch:
git checkout -b test-ci-
Make a small change (e.g., add a comment to a source file)
-
Commit and push:
git add .
git commit -m "test: trigger CI workflow"
git push origin test-ci- Open a pull request on GitHub and confirm all five checks turn green.
This workflow triggers automatically on push to main and can also be triggered manually. It requires the STELLAR_SECRET_KEY secret.
The workflow:
- Detects which contracts changed (or uses your manual input)
- Builds each contract to WASM
- Runs
stellar contract optimizeon the WASM - Runs
stellar contract deployusing thedeployeridentity imported fromSTELLAR_SECRET_KEY
To trigger manually:
- Go to Actions tab → Deploy to Testnet
- Click Run workflow
- Optionally specify a comma-separated list of contract names (leave blank to deploy all changed contracts)
- Verify the deployment summary in the workflow run output
This workflow runs on a schedule (every Monday) and can be triggered manually. It uses the stellar CLI (not soroban) and requires MAINNET_DEPLOYER_IDENTITY and/or TESTNET_DEPLOYER_IDENTITY.
To test with a dry run:
- Go to Actions tab → Extend Contract TTLs
- Click Run workflow
- Set Network to
testnet - Set Dry run to
true - Verify the workflow completes without errors
- Go to Actions tab → Security Audit
- Click Run workflow → Run workflow
- Verify the audit completes successfully
- Check for any security issues in the summary
GitHub automatically sends email notifications for failed workflow runs, security issues, and pull request reviews. Configure in Settings → Notifications.
-
Create a Slack Incoming Webhook for your workspace and copy the URL.
-
Add it as a repository secret:
- Name:
SLACK_WEBHOOK_URL - Value: Your webhook URL
- Name:
-
Add a notification step to any workflow:
- name: Notify Slack on failure
if: failure()
uses: slackapi/slack-github-action@v1
with:
webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }}
payload: |
{
"text": "CI Failed for ${{ github.repository }}",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "❌ *CI Failed*\n*Repository:* ${{ github.repository }}\n*Branch:* ${{ github.ref }}\n*Commit:* ${{ github.sha }}"
}
}
]
}Create .github/dependabot.yml:
version: 2
updates:
- package-ecosystem: "cargo"
directory: "/"
schedule:
interval: "weekly"
open-pull-requests-limit: 10
labels:
- "dependencies"
- "rust"
commit-message:
prefix: "chore"
include: "scope"Then commit and push:
git add .github/dependabot.yml
git commit -m "chore: add Dependabot configuration"
git push- Review Security Audit Results — check Actions → Security Audit, review any created issues, and update vulnerable dependencies.
- Review Dependabot PRs — check changelogs and merge after CI passes.
- Review Deployment History — check deployment artifacts, verify contract IDs are documented, and clean up old artifacts if needed.
- Update Workflows — check for GitHub Actions version updates, update Rust and
stellar-cliversions as needed, and review caching strategies.
- Security Review — run a comprehensive security audit, review all dependencies, and update security policies.
- Performance Review — analyze CI run times, optimize slow jobs, and review caching effectiveness.
Problem: CI fails with "required checks not found"
Solution:
- Let the CI run complete at least once so the job names are registered
- Then add the checks to branch protection
- Status checks must exist before GitHub allows them to be required
Problem: deploy-testnet.yml exits with an error about the deployer identity or missing secret
Solution:
- Confirm
STELLAR_SECRET_KEYis added under Settings → Secrets and variables → Actions - Check the secret name is exactly
STELLAR_SECRET_KEY(case-sensitive) - Confirm the value starts with
S(Stellar secret key format) - The workflow imports this key with
stellar keys add deployer --secret-key "$STELLAR_SECRET_KEY"— if the key is malformed, that step will fail
Problem: extend-ttls.yml exits with an error about the deployer identity
Solution:
- Confirm both
MAINNET_DEPLOYER_IDENTITYandTESTNET_DEPLOYER_IDENTITYare configured in repository secrets - Verify neither value is empty — an empty identity will silently fail or produce a cryptic CLI error
- Check that the identity's account has enough XLM to cover the transaction fees on the target network
Problem: Multiple security issues created for the same vulnerabilities
Solution:
- The workflow checks for existing issues before creating new ones
- If duplicates occur, manually close the extras
- Open a PR to improve the deduplication logic in the workflow
Problem: Contracts don't compile to WASM
Solution:
- Test locally:
cargo build --release --target wasm32-unknown-unknown - Check for platform-specific dependencies
- Ensure all contracts use
#![no_std] - Review
soroban-sdkcompatibility
Problem: Deployment workflow times out
Solution:
- The workflow only deploys changed contracts automatically — verify the change detection is working
- Use the manual trigger to deploy specific contracts by name
- Optimize WASM binaries before deployment
- Consider parallel deployment for large contract sets
Create a separate workflow for mainnet releases:
# .github/workflows/deploy-mainnet.yml
name: Deploy to Mainnet
on:
release:
types: [published]
env:
STELLAR_NETWORK_PASSPHRASE: "Public Global Stellar Network ; September 2015"
STELLAR_RPC_URL: "https://soroban-mainnet.stellar.org"
# ... rest of deployment workflow
# Use secrets.MAINNET_DEPLOYER_IDENTITY, not STELLAR_SECRET_KEYTest across multiple Rust versions:
test:
strategy:
matrix:
rust: [stable, beta, nightly]
steps:
- uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ matrix.rust }}
- run: cargo test --workspaceRun workflows only when contract files change:
on:
push:
paths:
- 'contracts/**'
- 'Cargo.toml'
- 'Cargo.lock'- Test locally first — run all CI checks locally before pushing. See
.github/workflows/README.mdfor the exact commands. - Keep secrets secure — never commit secrets, rotate them regularly, and use separate accounts for testnet and mainnet.
- Use the Secrets Reference table — when adding a new workflow that needs credentials, update the table at the top of this document at the same time.
- Monitor CI performance — review workflow run times, optimize caching, and parallelize where possible.
- Stay updated — keep GitHub Actions versions, Rust, and
stellar-clicurrent and review security advisories weekly. - Document changes — update this file and the workflow README whenever secrets or CLI commands change.
- GitHub Actions Issues: GitHub Community Forum
- Stellar CLI Issues: Stellar Discord
- Security Issues: Create a private security advisory in the repository
Use this checklist to verify your setup is complete:
-
STELLAR_SECRET_KEYsecret configured (used bydeploy-testnet.yml) -
TESTNET_DEPLOYER_IDENTITYsecret configured (used byextend-ttls.yml) -
MAINNET_DEPLOYER_IDENTITYsecret configured (used byextend-ttls.yml) - GitHub Actions enabled with read and write permissions
- Branch protection rules configured for
main - Required status checks added to branch protection (
CI Success,Format Check,Clippy Lint,Test Suite,Build WASM) - CI workflow tested with a pull request
- Deployment workflow tested (manual trigger with dry run)
- TTL extension workflow tested (manual trigger, testnet, dry run)
- Security audit workflow tested
- Notifications configured (email/Slack)
- Dependabot configured (optional)
- Team members have appropriate access levels
- Monitoring and maintenance schedule established
After completing this setup:
- Create a test PR to verify all CI checks pass
- Trigger a manual testnet deployment to verify
STELLAR_SECRET_KEYis working - Run a dry-run TTL extension to verify
TESTNET_DEPLOYER_IDENTITYis working - Review the security audit results
- Train team members on the CI/CD process and point them to the Secrets Reference table first