Skip to content

Migrate secrets from sops/age to Doppler with per-env R2 and Mongo access - #141

Merged
SibeiC merged 1 commit into
masterfrom
feat/doppler-migration
Jun 12, 2026
Merged

SibeiC merged 1 commit into
masterfrom
feat/doppler-migration

Conversation

@SibeiC

@SibeiC SibeiC commented Jun 12, 2026

Copy link
Copy Markdown
Owner

Summary

Replaces the sops/age-encrypted .env.enc pipeline with Doppler (project api-server, configs dev/prd), and splits Cloudflare R2 and MongoDB access per environment.

  • Runtime: image bundles the Doppler CLI; entrypoint.sh is now doppler run -- java -jar (with an encrypted --fallback file for restart resilience). Compose feeds DOPPLER_TOKEN from /opt/api-server/doppler.env.
  • Ansible: playbook fetches the TLS keystore password + nginx proxy secret from Doppler — via the service token in CI, or the logged-in doppler CLI on laptop runs (dev/prd config picked by install mode). All sops/age tasks, the age key prompt, and community.sops are gone.
  • CI: deploy payload no longer ships .env.enc/.sops.yaml; DOPPLER_TOKEN replaces AGE_PRIVATE_KEY + TLS_KEYSTORE_PASSWORD; docker job drops the sops build stage.
  • Per-env access: dev uses a new api-server-dev Atlas user/database and a bucket-scoped key for the new api-server-dev R2 bucket (7-day object retention); prod gets its own bucket-scoped key and a readWrite-on-api-server-only Atlas user. DNS key stays shared.
  • Code: camelCase @Value keys renamed to kebab-case (cloudflare.r2.account-id, …) so UPPER_SNAKE env vars bind via Spring relaxed binding; spring.config.import of .env and the hard-coded Mongo database name removed.
  • Fix: hosts.ini pins ansible_python_interpreter=/usr/bin/python3 — interpreter discovery picked python3.14, which panics importing the apt cryptography stack needed by openssl_pkcs12.

Verification

  • Full suite passes with no secrets in the environment (tests are mock/Testcontainers-only)
  • doppler run -- mvn spring-boot:run boots the dev profile; actuator health shows Mongo + mail UP
  • Smoke-tested both credential sets: dev R2 key does put/get/delete in api-server-dev and is AccessDenied on the prod bucket; prd key lists the prod bucket; dev Mongo user writes to api-server-dev; prd user reads api-server
  • ./install-dev.sh converges via the laptop Doppler path; nginx X-Proxy-Secret hash-matches the dev config
  • ansible-lint (production profile) passes

Post-merge

Tag deploy will use the DOPPLER_TOKEN repo secret (already set). After a healthy prod deploy: delete the old shared Atlas user + account-wide R2 keys, and remove the AGE_PRIVATE_KEY/TLS_KEYSTORE_PASSWORD GitHub secrets.

🤖 Generated with Claude Code

…cess

Secrets now live in Doppler project api-server (configs dev/prd) instead of
a sops/age-encrypted .env.enc:

- Container runs `doppler run -- java` (CLI baked into the image); compose
  feeds DOPPLER_TOKEN from /opt/api-server/doppler.env written by ansible
- Playbook fetches the keystore password + nginx proxy secret from Doppler
  (service token in CI, logged-in CLI on laptops); sops/age tasks removed
- CI ships no .env.enc and passes DOPPLER_TOKEN instead of AGE/TLS secrets
- Dev and prod use separate Mongo users/databases and bucket-scoped R2 keys
  (api-server-dev bucket has a 7-day object retention rule)
- @value keys renamed to kebab-case so UPPER_SNAKE env vars bind cleanly
- hosts.ini pins ansible_python_interpreter to the distro python (3.14
  discovery breaks the apt cryptography stack needed by openssl_pkcs12)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 12, 2026 02:59

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Migrates secret management and deployment flow from sops/age-encrypted .env.enc to Doppler (dev/prd), updating runtime, Ansible, and CI/CD to fetch/inject secrets via DOPPLER_TOKEN, and aligning Spring property keys with env-var-friendly naming.

Changes:

  • Replaces .env.enc/sops/age usage with Doppler-driven secret injection in container startup (doppler run) and Ansible secret retrieval (API token in CI vs CLI locally).
  • Updates Spring @Value keys to kebab-case for Cloudflare DNS/R2 settings and removes .env import + hard-coded Mongo DB name.
  • Removes sops/age tooling and artifacts from hooks, templates, Docker build, and GitHub Actions deploy payload.

Reviewed changes

Copilot reviewed 19 out of 19 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
templates/.sops.yaml.template Removes sops template (no longer needed with Doppler).
src/main/resources/application.properties Drops .env import; documents Doppler-provided env secrets.
src/main/java/com/chencraft/common/service/api/CloudflareWebClient.java Renames Cloudflare DNS property keys to kebab-case.
src/main/java/com/chencraft/common/config/S3Config.java Renames Cloudflare R2 property keys to kebab-case.
scripts/age_keygen.sh Removes age keygen + .sops.yaml generation script.
requirements.yml Removes community.sops collection dependency.
README.md Updates local/dev/prod secret workflow documentation for Doppler.
playbook.yml Replaces sops decrypt flow with Doppler API/CLI fetch; writes /opt/api-server/doppler.env for compose.
hosts.ini Pins ansible_python_interpreter to /usr/bin/python3 for the local target.
entrypoint.sh Switches container startup to doppler run -- java -jar ....
doppler.yaml Adds committed Doppler repo binding for dev config.
Dockerfile Removes sops stage; bundles Doppler CLI into runtime image.
docker-compose.yml Adds env_file pointing to /opt/api-server/doppler.env for DOPPLER_TOKEN.
CLAUDE.md Updates contributor/dev notes to use doppler run.
.sops.yaml Removes sops rules file.
.github/workflows/maven.yml Removes sops assets/steps; switches deploy secret to DOPPLER_TOKEN.
.githooks/pre-commit Removes auto-encrypt .env → .env.enc logic.
.githooks/post-checkout Removes auto-decrypt/install sops/age; nudges Doppler setup instead.
.env.enc Removes encrypted env payload from repo and deploy.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread entrypoint.sh
Comment on lines +4 to +6
# Inject secrets from Doppler (project/config implied by DOPPLER_TOKEN service token).
# --fallback keeps restarts working if api.doppler.com is briefly unreachable.
exec doppler run --fallback /opt/api-server/doppler-fallback.json -- java -jar api-server.jar

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Verified locally with doppler CLI v3.76.0: doppler run --fallback <path> creates (and refreshes) the encrypted fallback file itself on every successful secrets fetch — it doesn't need to pre-exist, and a missing file doesn't block startup while api.doppler.com is reachable. The file only gets read when the API is unreachable, in which case a prior successful start has already written it. So no playbook/image task is needed; first boot creates it on the mounted /opt/api-server volume. Pushed a clarifying comment to entrypoint.sh.

@github-actions

Copy link
Copy Markdown

Qodana for JVM

It seems all right 👌

No new problems were found according to the checks applied

💡 Qodana analysis was run in the pull request mode: only the changed files were checked
☁️ View the detailed Qodana report

Contact Qodana team

Contact us at qodana-support@jetbrains.com

@SibeiC
SibeiC merged commit 3b91e9e into master Jun 12, 2026
11 checks passed
@SibeiC
SibeiC deleted the feat/doppler-migration branch June 12, 2026 03:05
SibeiC added a commit that referenced this pull request Jun 12, 2026
Addresses PR #141 review comment: the fallback file is auto-created by
doppler run on each successful fetch and need not pre-exist.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
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.

2 participants