Skip to content

feat: automate TLS cert issuance via certbot (#87) - #95

Merged
izolyte merged 2 commits into
mainfrom
feat/87-letsencrypt-tls
Jul 22, 2026
Merged

feat: automate TLS cert issuance via certbot (#87)#95
izolyte merged 2 commits into
mainfrom
feat/87-letsencrypt-tls

Conversation

@izolyte

@izolyte izolyte commented Jul 22, 2026

Copy link
Copy Markdown
Owner

What

Prod (getsynxor.com) still serves the self-signed IP cert (CN=209.97.172.226), so browsers warn and wss only works after a click-through. This wires up a real Let's Encrypt cert issued and renewed by an on-demand certbot container, terminating the last blocker on Tier 2 TLS of #87.

The design avoids root entirely: certbot writes into the deploy-owned nginx/certs mount (the deploy user is in the docker group), so no sudo and no /etc/letsencrypt access is needed — which matters given root on the droplet is only reachable via the DO recovery console.

Changes

  • nginx template: the :80 server block serves /.well-known/acme-challenge/ from a webroot shared with certbot, ahead of the HTTPS redirect, so the http-01 challenge resolves over plain HTTP. In dev the webroot is unmounted and the path just 404s — harmless.
  • prod compose: new certbot service (no daemon — command: true, driven on demand) plus a certbot_webroot volume mounted into both nginx (:ro) and certbot. certbot's config dir maps onto nginx/certs, so issued certs land at live/getsynxor.com/{fullchain,privkey}.pem exactly where TLS_CERT_PATH/TLS_KEY_PATH expect them.
  • docs: nginx/README.md Production section rewritten with the real first-issue commands (staging dry-run → real issue), the .env cert paths, and a weekly deploy-crontab renewal line.

Deploy steps (operator, after merge + CD ships the files)

Run from $DEPLOY_PATH on the droplet as deploy, no sudo:

# nginx first, so :80 serves the challenge location
docker compose -f docker-compose.prod.yml up -d nginx

# staging dry-run, then real issue (drop --staging)
docker compose -f docker-compose.prod.yml run --rm certbot certonly \
  --webroot -w /var/www/certbot -d getsynxor.com -d www.getsynxor.com \
  --email <operator-email> --agree-tos --no-eff-email

Then set in the host .env and reload:

TLS_CERT_PATH=/etc/nginx/certs/live/getsynxor.com/fullchain.pem
TLS_KEY_PATH=/etc/nginx/certs/live/getsynxor.com/privkey.pem
docker compose -f docker-compose.prod.yml up -d nginx

Weekly renewal via deploy crontab — see nginx/README.md.

Verification

  • Config-only change (nginx conf + compose + docs); no app code touched. nginx -t not run locally (no Docker on the dev box) — the challenge location is a standard location { root … } and validates when the stack comes up on the droplet.
  • DNS + reachability confirmed: getsynxor.com and www.getsynxor.com both resolve to the droplet, ports 80/443 open — http-01 will validate for both names.

Part of #87 (Tier 2 — real TLS cert + auto-renew). HSTS header left as a follow-up so it can be env-gated without affecting local dev.

Summary by CodeRabbit

  • New Features

    • Added Let’s Encrypt certificate issuance and renewal support for production deployments.
    • Enabled HTTP-01 validation while preserving automatic HTTP-to-HTTPS redirection.
    • Added shared certificate challenge handling between the web server and certificate service.
  • Documentation

    • Added instructions for certificate setup, staging validation, renewal, and web server reloads.

Replace the self-signed prod cert with a real Let's Encrypt cert issued and
renewed by an on-demand certbot container. certbot writes into the deploy-owned
nginx/certs mount, so issuance needs no root or /etc/letsencrypt access.

- nginx :80 serves /.well-known/acme-challenge/ from a webroot shared with
  certbot, ahead of the HTTPS redirect (http-01 challenge)
- add certbot service + certbot_webroot volume to the prod compose
- document first-issue + weekly renewal (deploy crontab) in nginx/README.md
@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@izolyte, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 52 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 0777d1fc-2505-4569-b69e-e41ed3359e3b

📥 Commits

Reviewing files that changed from the base of the PR and between cafd588 and 478c21b.

📒 Files selected for processing (1)
  • deploy/docker-compose.prod.yml
📝 Walkthrough

Walkthrough

Production Compose now provisions an on-demand Certbot service with shared ACME webroot storage. Nginx serves HTTP-01 challenge files before redirecting other HTTP traffic, and the README documents certificate issuance, configuration, reloads, and renewal.

Changes

Let’s Encrypt TLS integration

Layer / File(s) Summary
Certbot Compose integration
deploy/docker-compose.prod.yml
Adds the Certbot service, shared certbot_webroot volume, certificate mount, and read-only Nginx webroot mount.
Nginx ACME workflow and operations
nginx/templates/default.conf.template, nginx/README.md
Serves HTTP-01 challenge files over port 80 while redirecting other requests, and documents staging, issuance, environment variables, reloads, and weekly renewal.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Certbot as Certbot service
  participant Webroot as certbot_webroot
  participant LetsEncrypt as Let's Encrypt
  participant Nginx
  Certbot->>Webroot: write HTTP-01 challenge file
  LetsEncrypt->>Nginx: request ACME challenge over HTTP
  Nginx->>Webroot: read challenge file
  Nginx-->>LetsEncrypt: return challenge response
  Certbot->>Certbot: write certificates to /etc/letsencrypt
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: automating TLS certificate issuance with Certbot.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/87-letsencrypt-tls

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.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🧹 Nitpick comments (1)
deploy/docker-compose.prod.yml (1)

54-54: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Pin the Certbot image. certbot/certbot:latest is mutable; use a versioned tag or digest so issuance/renewal runs don't pick up an unexpected upstream build.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@deploy/docker-compose.prod.yml` at line 54, Update the Certbot service image
reference from the mutable certbot/certbot:latest tag to a specific versioned
tag or immutable digest, preserving the existing Certbot service configuration.

Source: MCP tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@deploy/docker-compose.prod.yml`:
- Around line 60-61: Restore the Certbot image entrypoint in
deploy/docker-compose.prod.yml by removing the empty entrypoint override, while
preserving the existing command only if needed for normal service behavior. The
certonly and renew command sites in nginx/README.md at lines 59-68 and 88-89
require no direct changes; they must continue relying on the restored image
entrypoint.

---

Nitpick comments:
In `@deploy/docker-compose.prod.yml`:
- Line 54: Update the Certbot service image reference from the mutable
certbot/certbot:latest tag to a specific versioned tag or immutable digest,
preserving the existing Certbot service configuration.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 1ee1b53e-d7ae-4035-bab2-f68efab29623

📥 Commits

Reviewing files that changed from the base of the PR and between b7ca3c3 and cafd588.

📒 Files selected for processing (3)
  • deploy/docker-compose.prod.yml
  • nginx/README.md
  • nginx/templates/default.conf.template

Comment thread deploy/docker-compose.prod.yml Outdated
)

The empty entrypoint override broke the documented 'run --rm certbot certonly'
and 'renew' commands — with entrypoint cleared, Compose execs 'certonly' as the
binary instead of 'certbot certonly'. Restore the image's default entrypoint and
instead use a 'certbot' profile to keep the service out of a bare 'compose up'
(explicitly named services in 'run' start regardless of profile).
@izolyte
izolyte merged commit 62f2c53 into main Jul 22, 2026
6 checks passed
@izolyte
izolyte deleted the feat/87-letsencrypt-tls branch July 22, 2026 17:08
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.

1 participant