You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Prepare the v1.0.0 release: versioned builds, clean config, public image - #761
Gets the repo ready to cut v1.0.0 and publish it to public ECR, with Docker Hub kept for existing users. No ingestion or API behavior changes. The docs that describe all of this are in #762, stacked on this PR.
TL;DR
The binary knows its version. wallet-backend version prints it, and every service logs it at startup.
Config is cleaned up before semver freezes it: 6 flags removed, 3 defaults changed, 2 validations added. See the tables below for what you may need to change in your env.
The Docker image is production-shaped: static binary, slim base, non-root, 172 MB.
Promoting a release also publishes public.ecr.aws/stellar/wallet-backend:<version> and :latest (primary; the repository is declared in stellar/terraform) and the same digest to Docker Hub stellar/wallet-backend, which stays until it is deprecated.
docker compose up is now a testnet quickstart on the published image.
If you deploy this: what to change in your env
You set
What happens now
Do
NETWORK
Ignored. The ingest lock key comes from NETWORK_PASSPHRASE.
START_LEDGER or END_LEDGER on a live ingester, any value including 0
Startup fails with a clear error.
Unset them on live; keep them on backfill
Nothing for LEDGER_BACKEND_TYPE
Default is now rpc, not datastore.
Set LEDGER_BACKEND_TYPE=datastore explicitly if you rely on it
Nothing for DATASTORE_BUCKET_PATH with datastore
Startup fails; there is no default bucket any more.
Set it to your network's bucket
Nothing for DB_MAX_CONNS on a live ingester
Default is now 12 (from main), and live mode refuses to start below 9: the persist pipeline holds 7 sibling transactions, a coordinator and the lock session.
Nothing, unless you pinned a value below 9
Nothing for LOG_LEVEL
INFO, and it is actually applied. Before, the default never reached the logger and services ran at TRACE.
Nothing, unless you want DEBUG
Our dev, staging, and prod manifests already set the backend type, bucket path, and log level explicitly, so nothing breaks there. The ingest lock key changes once; ingest uses a Recreate strategy, so no two ingesters overlap during the rollout.
A promoted image reports the rc it was built as (for example v1.2.0-rc.2), because promotion copies the soaked digest instead of rebuilding. The release and the image tags carry the final version.
Config cleanup, in full
Change
Why
Removed --network
Only fed the advisory-lock ID; derived from the passphrase now
Removed --server-base-url, --supported-assets
Parsed and never read
Removed --latest-ledger-cursor-name
Deprecated no-op
Removed --tracker-dsn, --stellar-environment, the Sentry client, the AppTracker plumbing, the sentry-go dependency
Not used. The 500/503 paths and panic recovery still log the error
--ledger-backend-type defaults to rpc on ingest and protocol-migrate
One story for every command: RPC works out of the box, datastore is explicit
--datastore-bucket-path has no default; required only with datastore
The old default pointed at pubnet while the passphrase and archive defaulted to testnet
Live mode rejects an explicitly set --start-ledger / --end-ledger, even 0; backfill validates the range, stays below the uint32 ceiling, and splits batches without overflow
Live mode silently ignored them
--log-level defaults to INFO and the default is applied
The setter only applied the level when the flag was set explicitly
--compression-max-chunks and RETENTION_PERIOD help text match the code
0 leaves the compression job unchanged; an empty retention period removes the policy
.env.example lists every variable with a one-line comment; values with spaces are quoted
Works with source .env and with Compose
Image
Before
After
Build
-gcflags=all=-N -l, Delve installed
CGO_ENABLED=0 -trimpath -ldflags "-s -w"; BUILD_MODE=debug opts into Delve
Unchanged shape: dispatch publish-prerelease with vX.Y.Z-rc.N, soak on staging, dispatch promote-release. What is new:
promote-release logs into public ECR through the shared sdf-ecr-login action (login-public-ecr: true, the same path friendbot uses) and copies the production digest to public.ecr.aws/stellar/wallet-backend:<version> and :latest. It then copies the same digest to Docker Hub using the existing DOCKERHUB_* secrets. All three tag checks run before any tag is created. Each proceeds on an explicit not-found or on a tag that already holds the staging digest, so a promotion that failed partway can be re-run, and a tag with any other digest stops it. Each copy is digest-verified. Promotions run one at a time, and latest moves only when the release is the highest version released so far (every release is checked, and a failed lookup stops the run), so a patch for an older line never moves it backwards. Docker Hub is kept for existing users until it is deprecated; public ECR is primary.
The GitHub release starts with an Images and Tested with block, then the generated notes. Fill the two <fill in> lines (stellar-rpc, protocol) after promotion.
.github/release.yml groups notes by label: breaking, feature, fix, docs, dependencies, ci. Dependabot PRs get dependencies. The PR template asks for a label.
CodeQL stops scanning develop.
Compose
docker-compose.yaml: db, stellar-rpc (testnet), migrate, ingest, api from public.ecr.aws/stellar/wallet-backend:${WALLET_BACKEND_VERSION:-latest}.
docker-compose.dev.yaml: local debug build and the Delve port.
How to review
Each commit is one concern and reads on its own, in this order:
Version injection and the version command
Config cleanup
Dockerfile and Makefile
Workflows and release notes
Compose
Help-text fixes for the compression and retention knobs
Reject explicitly supplied zero ledger flags in live mode
cmd/ingest.go:164
This checks only the resulting integer values, so an explicitly supplied --start-ledger=0 or --end-ledger=0 is silently accepted in live mode even though the new contract says live mode rejects these flags. Check the Cobra flag's Changed state in addition to the non-zero values.
RPC migration lacks a default RPC URL
cmd/protocol_migrate.go:93
With this default, protocol-migrate selects the RPC backend but rpc-url still defaults to an empty string (line 85), and the pre-run validation rejects that at line 129. A flag-less RPC migration therefore fails instead of working out of the box as this change intends; give rpc-url the same localhost default as the other RPC commands (or reuse RPCURLOption).
Apply INFO logger default when LOG_LEVEL is unset
cmd/utils/global_options.go:53
Changing the config default to INFO does not change the process logger's default: preConfigureLogger still sets it to TRACE, and SetConfigOptionLogLevel only calls SetLevel when the option is explicitly set. Consequently, a normal serve/ingest launch with no LOG_LEVEL still emits TRACE-level logs, contrary to this new default; apply the parsed default level as well.
Use consistent fallback for empty Git commit
cmd/root.go:57
For an un-injected build, cfg.GitCommit is empty, so service startup logs wallet-backend dev (commit ), while wallet-backend version normalizes the same value to unknown. This makes the advertised build identity inconsistent and leaves an unhelpful blank field; use the same fallback here.
Because this root hook runs before each command's PersistentPreRunE, an invalid service configuration logs the build-identity INFO line here and then Execute prints Error: .... That makes cases such as ingest --ingestion-mode=live --start-ledger=5 produce two lines, contradicting the promised one-line config error; move this log after command-specific validation (or otherwise suppress it when validation fails).
On the overview-only findings Copilot listed without a thread:
Apply INFO logger default: already fixed in 31ca373; the setter now applies the parsed level whether or not the flag was set.
Use a consistent fallback for an empty commit: done in ad155a0; the startup line prints unknown like the version command.
Reject an explicit --start-ledger=0 in live mode: not changing. Zero is the documented "unset" value for both flags and the env-var path cannot distinguish explicit zero from absent.
Default rpc-url on protocol-migrate: not changing. An empty rpc-url is what tells a datastore run to skip RPC metadata and the tip gauge; a localhost default would make those runs call an RPC that is not there. The rpc backend fails fast with a clear message instead.
Identity log before validation: keeping it. Knowing which build rejected a config is useful on the failure path; the description now says "a single Error: line" follows the startup line.
The service startup log here omits the Go toolchain version, while wallet-backend version includes it and the PR description says every service logs the same build-identity line. As written, serve, ingest, and the migration commands do not expose the complete identity; include runtime.Version() in this log as well.
aditya1702
changed the title
Release prep for v1.0.0: version injection, config cleanup, production image, Docker Hub publishing
Prepare the v1.0.0 release: versioned builds, clean config, public image
Oct 5, 2026
Cross-registry promotion cannot safely resume after partial failure
.github/workflows/promote-release.yml:249
A failure after the production/public copy but during Docker Hub publication leaves a partially promoted release, and a retry is then blocked because the existing production and public tags fail the preflight checks. Make the cross-registry steps resumable by accepting an existing destination tag only when its digest matches the source (and skipping that copy), or provide an equivalent recovery path.
main.Version and main.GitCommit are both set through ldflags; a build
without them reports "dev". `wallet-backend version` prints the version,
commit, and Go toolchain. Service commands log the same identity on
startup. A config error now prints one line to stderr and exits 1
instead of panicking with a stack trace.
…face freezes
Removed: --network (the advisory-lock ID now derives from the network
passphrase), --server-base-url and --supported-assets (parsed, never
read), --latest-ledger-cursor-name (deprecated no-op).
Changed: --ledger-backend-type defaults to rpc for ingest and
protocol-migrate; --datastore-bucket-path has no default and is required
only with the datastore backend; live mode rejects --start-ledger and
--end-ledger instead of ignoring them; backfill validates its range;
--stellar-environment is optional; --log-level defaults to INFO.
.env.example lists every variable with a one-line comment.
Release builds use CGO_ENABLED=0, -trimpath and -s -w. BUILD_MODE=debug
adds Delve and disables optimizations for docker-compose.dev.yaml. The
runtime image is debian:bookworm-slim with ca-certificates only, runs as
UID 10001, exposes 8001, and carries OCI labels. The Makefile passes
VERSION and BUILD_MODE through and drops the unused STELLAR_CORE_VERSION.
promote-release copies the production digest to
docker.io/stellar/wallet-backend:<version> and :latest, refuses to
overwrite an existing tag, and verifies the digest. The GitHub release
starts with an Images and Tested-with block. .github/release.yml groups
notes by label; Dependabot PRs get the dependencies label. CodeQL no
longer scans the develop branch.
docker-compose.yaml runs db, stellar-rpc, migrate, ingest and api from
stellar/wallet-backend:${WALLET_BACKEND_VERSION:-latest}.
docker-compose.dev.yaml overrides it with a local debug build and the
Delve port.
COMPRESSION_MAX_CHUNKS=0 leaves the job's setting unchanged rather than
meaning unlimited, and an empty RETENTION_PERIOD removes the retention
policy rather than keeping the current one.
SetConfigOptionLogLevel only called SetLevel when the option was set
explicitly, so serve and ingest ignored the INFO default and logged at
TRACE unless LOG_LEVEL was given.
…nt32 range check
The Docker Hub existence check proceeds only on an explicit not-found
answer and fails on any other inspect error. The CI integration image
gets VERSION=integration-test. The documented Delve invocation overrides
the entrypoint, since the image's entrypoint is wallet-backend. Values
with spaces in .env.example are quoted so the file works with source
and with Compose. Backfill rejects an end ledger above uint32. The
startup identity line prints "unknown" for an empty commit, matching
the version command.
… flags
The AppTracker interface existed only to forward errors to Sentry.
Removing it drops the two flags, the sentry-go dependency, the
internal/apptracker package, and the tracker parameter threaded through
the HTTP error helpers, middleware, health handler, and ingest config.
Errors are still logged where they were captured before.
promote-release logs into public ECR through the shared sdf-ecr-login
action and copies the production digest to
public.ecr.aws/stellar/wallet-backend:<version> and :latest. That
repository is managed in stellar/terraform. The compose quickstart and
the release notes pull from it.
Public ECR stays the primary registry. promote-release also copies the
same production digest to docker.io/stellar/wallet-backend:<version>
and :latest with the same fail-closed existence check, until Docker Hub
is deprecated. The release notes list both pull commands.
…ring, overflow-safe backfill ranges
Every image-existence check in publish-prerelease and promote-release
now proceeds only on an explicit not-found answer and fails on any
other error. promote-release runs all three existence checks before it
creates any tag, so a failed check leaves nothing half-promoted, and it
uses one global concurrency group so :latest cannot move backwards.
Live ingest rejects an explicitly supplied --start-ledger or
--end-ledger, including 0, via flag or env var. Backfill rejects an end
ledger at the uint32 ceiling and splits batches without overflow.
…op at the ceiling
promote-release compares the release version with the highest existing
non-prerelease and moves :latest only when this release is the newest,
so promoting a patch for an older line publishes its tag without moving
:latest. The per-ledger loop in backfill stops after EndLedger instead
of relying on a uint32 counter that wraps at the ceiling.
A zero --backfill-batch-size would make the batch splitter loop
forever; a zero insert batch size would never flush. Both must be
between 1 and MaxUint32, and --backfill-workers must not be negative.
…fails on gh errors
The :latest check pages through every release and stops on a gh failure
instead of reading it as no releases. The tag guards accept a tag that
already holds the staging digest, so a run that failed after a partial
publish can be re-run, and treat 'no such manifest' as not found.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Gets the repo ready to cut
v1.0.0and publish it to public ECR, with Docker Hub kept for existing users. No ingestion or API behavior changes. The docs that describe all of this are in #762, stacked on this PR.TL;DR
wallet-backend versionprints it, and every service logs it at startup.public.ecr.aws/stellar/wallet-backend:<version>and:latest(primary; the repository is declared instellar/terraform) and the same digest to Docker Hubstellar/wallet-backend, which stays until it is deprecated.docker compose upis now a testnet quickstart on the published image.If you deploy this: what to change in your env
NETWORKNETWORK_PASSPHRASE.TRACKER_DSN,STELLAR_ENVIRONMENTSERVER_BASE_URL,SUPPORTED_ASSETS,LATEST_LEDGER_CURSOR_NAMESTART_LEDGERorEND_LEDGERon a live ingester, any value including0LEDGER_BACKEND_TYPErpc, notdatastore.LEDGER_BACKEND_TYPE=datastoreexplicitly if you rely on itDATASTORE_BUCKET_PATHwith datastoreDB_MAX_CONNSon a live ingestermain), and live mode refuses to start below 9: the persist pipeline holds 7 sibling transactions, a coordinator and the lock session.LOG_LEVELINFO, and it is actually applied. Before, the default never reached the logger and services ran atTRACE.DEBUGOur dev, staging, and prod manifests already set the backend type, bucket path, and log level explicitly, so nothing breaks there. The ingest lock key changes once; ingest uses a
Recreatestrategy, so no two ingesters overlap during the rollout.Version
wallet-backend versionwallet-backend v1.2.3 (commit abc1234, go1.25.9)wallet-backend v1.2.3 (commit abc1234)org.opencontainers.image.version,.revision,.source,.licensesdev, commitunknownA promoted image reports the rc it was built as (for example
v1.2.0-rc.2), because promotion copies the soaked digest instead of rebuilding. The release and the image tags carry the final version.Config cleanup, in full
--network--server-base-url,--supported-assets--latest-ledger-cursor-name--tracker-dsn,--stellar-environment, the Sentry client, theAppTrackerplumbing, thesentry-godependency--ledger-backend-typedefaults torpconingestandprotocol-migrate--datastore-bucket-pathhas no default; required only withdatastore--start-ledger/--end-ledger, even0; backfill validates the range, stays below the uint32 ceiling, and splits batches without overflow--log-leveldefaults toINFOand the default is applied--compression-max-chunksandRETENTION_PERIODhelp text match the code0leaves the compression job unchanged; an empty retention period removes the policy.env.examplelists every variable with a one-line comment; values with spaces are quotedsource .envand with ComposeImage
-gcflags=all=-N -l, Delve installedCGO_ENABLED=0 -trimpath -ldflags "-s -w";BUILD_MODE=debugopts into Delveubuntu:jammy+ curl, wget, gnupg, Stellar apt reposdebian:bookworm-slim+ca-certificates/app/configcopied in.dockerignoreaddedRelease flow
Unchanged shape: dispatch
publish-prereleasewithvX.Y.Z-rc.N, soak on staging, dispatchpromote-release. What is new:promote-releaselogs into public ECR through the sharedsdf-ecr-loginaction (login-public-ecr: true, the same path friendbot uses) and copies the production digest topublic.ecr.aws/stellar/wallet-backend:<version>and:latest. It then copies the same digest to Docker Hub using the existingDOCKERHUB_*secrets. All three tag checks run before any tag is created. Each proceeds on an explicit not-found or on a tag that already holds the staging digest, so a promotion that failed partway can be re-run, and a tag with any other digest stops it. Each copy is digest-verified. Promotions run one at a time, andlatestmoves only when the release is the highest version released so far (every release is checked, and a failed lookup stops the run), so a patch for an older line never moves it backwards. Docker Hub is kept for existing users until it is deprecated; public ECR is primary.<fill in>lines (stellar-rpc, protocol) after promotion..github/release.ymlgroups notes by label:breaking,feature,fix,docs,dependencies,ci. Dependabot PRs getdependencies. The PR template asks for a label.develop.Compose
docker-compose.yaml:db,stellar-rpc(testnet),migrate,ingest,apifrompublic.ecr.aws/stellar/wallet-backend:${WALLET_BACKEND_VERSION:-latest}.docker-compose.dev.yaml: local debug build and the Delve port.How to review
Each commit is one concern and reads on its own, in this order:
versioncommandlatestfollows the highest release, batch loop ends at the ceilingmainafter the live-persist pipeline merged;.env.examplegainsLIVE_PERSIST_MAX_BATCH_SIZEand the pool minimumgherrors,no such manifestcounts as not foundversioncommand output and itsunknowncommit fallbackVerification
make check,make unit-testwith race detection, CI integration suite: green.docker buildthenwallet-backend versionprints the injected version;id -uis 10001.ingest --ingestion-mode=live --start-ledger=5exits 1 with a singleError:line after the startup line..env.exampleparses undersourceand as a Compose env file.Tracked internally.
🤖 Generated with Claude Code