[DevOps] Add GHCR publishing and DigitalOcean Terraform infrastructure#3
[DevOps] Add GHCR publishing and DigitalOcean Terraform infrastructure#3
Conversation
… and dependencies
…nt configurations - Updated .env.example for backend application settings - Enhanced .gitignore to exclude Terraform files - Modified Makefile to include Terraform commands - Expanded README.md to document DigitalOcean deployment - Updated docker-compose.yml to use GHCR image - Added Terraform configuration files for DigitalOcean resources - Created cloud-init.template for instance initialization - Implemented DigitalOcean droplet and volume resources in Terraform - Defined variables and outputs for Terraform modules
…gitalOcean provider version
…lation and key management
…cookie management
…tor related configurations
There was a problem hiding this comment.
Pull request overview
Adds end-to-end deployment infrastructure for DigitalOcean using Terraform + cloud-init, and introduces GHCR image publishing. Also updates the app’s health endpoint to report whether a yt-dlp cookies file is present, and adapts runtime/server wiring to support production rate limiting.
Changes:
- Add Terraform modules, account stack, and cloud-init provisioning to deploy the container on a DigitalOcean Droplet with optional persistent volume.
- Add GitHub Actions workflow to build/push the Docker image to GHCR and refactor Dockerfile for cargo-chef + Alpine-based runtime.
- Extend
/healthresponse with acookiesboolean and update tests/config helpers accordingly.
Reviewed changes
Copilot reviewed 28 out of 32 changed files in this pull request and generated 15 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/api/health_tests.rs | Adds assertions and a new test for cookies presence in /health. |
| tests/api/common.rs | Adds helper to create test state with an optional cookies file path. |
| src/models/health_model.rs | Extends Health model with cookies flag and updates constructor. |
| src/controllers/health_controller.rs | Computes cookies flag based on configured cookies file path. |
| src/app.rs | Starts Axum with ConnectInfo to support SmartIpKeyExtractor in prod rate limiting. |
| infra/digitalocean/components/versions.tf | Pins Terraform/provider version requirements for the module. |
| infra/digitalocean/components/variables.tf | Declares module inputs (DO, GHCR, runtime config, cookies presign URL). |
| infra/digitalocean/components/r-digitalocean_volume_attachment.tf | Attaches optional block storage volume to droplet. |
| infra/digitalocean/components/r-digitalocean_volume.tf | Defines optional persistent volume. |
| infra/digitalocean/components/r-digitalocean_droplet.tf | Defines droplet and injects rendered cloud-init user_data. |
| infra/digitalocean/components/provider.tf | Configures DigitalOcean provider for the module. |
| infra/digitalocean/components/outputs.tf | Exposes droplet ID and IPv4 from the module. |
| infra/digitalocean/components/locals.tf | Renders cloud-init template and sets tags/device path. |
| infra/digitalocean/accounts/naduns-team/variables.tf | Root-stack variable definitions for a specific account/environment. |
| infra/digitalocean/accounts/naduns-team/terraform.tfvars.example | Example tfvars for the root stack. |
| infra/digitalocean/accounts/naduns-team/outputs.tf | Pass-through outputs for droplet info. |
| infra/digitalocean/accounts/naduns-team/main.tf | Instantiates the components module with root variables. |
| infra/digitalocean/accounts/naduns-team/backend.tf | Configures remote state backend (S3-compatible/R2). |
| infra/digitalocean/accounts/naduns-team/.terraform.lock.hcl | Provider lockfile for the root stack. |
| infra/common/cloud-init.template | Bootstraps droplet: installs Docker, mounts volume, pulls/runs GHCR image, optionally downloads cookies. |
| infra/.terraform.lock.hcl | Additional provider lockfile at infra root. |
| docs/images/Themed-Architecture-Diagram.svg | Adds rendered infrastructure diagram. |
| docs/images/Themed-Architecture-Diagram-code.md | Adds Mermaid source for the diagram. |
| docker-compose.yml | Adjusts local compose to mount cookies file as a secret-like bind mount. |
| README.md | Documents new IaC layout and links to the infra diagram. |
| Makefile | Adds Terraform helper shell, prod Docker build/run targets, and cookies-file plumbing for compose. |
| Dockerfile | Replaces Debian build with cargo-chef + Alpine runtime, adds static ffmpeg and bgutil assets. |
| .gitignore | Ignores Terraform artifacts/secrets and local keys/cookies. |
| .github/workflows/publish-ghcr.yml | Adds workflow to build/push GHCR image on main. |
| .github/workflows/ci.yml | Renames clippy step (behavior unchanged). |
| .env.example | Reworks env example to include infra-related variables. |
| .dockerignore | Narrows README ignore rule to README.md only. |
Files not reviewed (2)
- infra/.terraform.lock.hcl: Language not supported
- infra/digitalocean/accounts/naduns-team/.terraform.lock.hcl: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| $(SAY) "$(GREEN):::Release build completed at $(BUILD_TIME) :::$(NC)" | ||
|
|
||
| ldeploy: lrelease ## Run release binary locally | ||
| deploy: lrelease ## Run release binary locally |
There was a problem hiding this comment.
deploy depends on lrelease, but that target no longer exists after the renames. This makes make deploy fail. Update the dependency to the renamed release target (and ensure the help text matches).
| deploy: lrelease ## Run release binary locally | |
| deploy: release ## Build and run release binary locally |
| RUST_LOG = "REPLACE_WITH_RUST_LOG" | ||
| YTDLP_PATH = "REPLACE_WITH_YTDLP_PATH" | ||
| YTDLP_OUTPUT_DIR = "REPLACE_WITH_YTDLP_OUTPUT_DIR" | ||
| YTDLP_POT_PROVIDER_URL = "REPLACE_WITH_YTDLP_POT_PROVIDER_URL" | ||
| YTDLP_TIMEOUT_SECS = "REPLACE_WITH_YTDLP_TIMEOUT_SECS" | ||
| YTDLP_COOKIES_FILE = "REPLACE_WITH_YTDLP_COOKIES_FILE" | ||
| YTDLP_EXTRACTOR_ARGS = "REPLACE_WITH_YTDLP_EXTRACTOR_ARGS" |
There was a problem hiding this comment.
The example uses env var names that don't match the current Rust config parsing. AppConfig::from_env() reads DOWNLOAD_DIR, but .env.example defines YTDLP_OUTPUT_DIR (and also YTDLP_TIMEOUT_SECS, which doesn't appear to be used). This will misconfigure local runs unless users manually correct it. Align the example keys with src/config.rs (or update AppConfig::from_env() to accept the new names).
| $(Q)DOCKER_BUILDKIT=1 docker buildx build \ | ||
| --builder $(BUILDER_NAME) \ | ||
| --platform $(PLATFORMS) \ | ||
| --platform linux/amd64 \ |
There was a problem hiding this comment.
The dbuild-prod help string and log message imply a multi-platform build using $(PLATFORMS), but the command hard-codes --platform linux/amd64. Either switch back to --platform $(PLATFORMS) (and handle multi-arch in the Dockerfile), or update the help/message/variable names to reflect amd64-only builds.
| --platform linux/amd64 \ | |
| --platform $(PLATFORMS) \ |
| DEVICE="${volume_device_by_id}" | ||
| for i in {1..30}; do | ||
| if [ -b "$DEVICE" ]; then | ||
| break | ||
| fi | ||
| sleep 2 | ||
| done | ||
|
|
||
| if [ ! -b "$DEVICE" ]; then | ||
| echo "ERROR: Volume failed to attach after 60 seconds." | ||
| exit 1 | ||
| fi |
There was a problem hiding this comment.
The script unconditionally waits for and requires the block volume device, and exits non-zero if it isn't attached. But the Terraform volume/attachment resources are conditional (count = var.volume_size_gib > 0 ? 1 : 0), so setting volume_size_gib = 0 will make cloud-init fail and leave the droplet unusable. Consider passing a boolean like enable_volume into the template and skipping the device/format/mount steps when disabled (or ensure volume is always attached).
| ```mermaid | ||
| --- | ||
| --- | ||
| config: | ||
| theme: neo-dark | ||
| look: neo | ||
| layout: dagre | ||
| --- |
There was a problem hiding this comment.
The Mermaid frontmatter block looks malformed: there are two consecutive --- lines before config:, which typically makes Mermaid config parsing fail. It should be a single frontmatter block (--- ... ---) followed by the diagram.
| ARG RUST_IMAGE=lukemathwalker/cargo-chef:latest-rust-alpine | ||
| ARG BIN=nadzu | ||
| ARG BGUTIL_VERSION=0.7.2 | ||
|
|
||
| FROM ${RUST_IMAGE} AS base | ||
| RUN apt-get update \ | ||
| && apt-get install -y --no-install-recommends \ | ||
| build-essential \ | ||
| ca-certificates \ | ||
| libzstd-dev \ | ||
| pkg-config \ | ||
| && rm -rf /var/lib/apt/lists/* | ||
| RUN cargo install --locked cargo-chef | ||
| FROM ${RUST_IMAGE} AS chef |
There was a problem hiding this comment.
The Dockerfile uses an unpinned third‑party build image lukemathwalker/cargo-chef:latest-rust-alpine, which is a mutable latest tag from a non-official namespace. If that image is ever compromised or retagged, your build pipeline could execute attacker-controlled code during image builds, allowing exfiltration of build secrets or injection of backdoors into the final production image. To mitigate this supply chain risk, pin this base image to an immutable identifier (e.g., a specific image digest) or switch to an official, vendor-maintained image that is version-pinned.
Description
Introduces DigitalOcean provisioning using Terraform. This enables seamless cloud deployment with
cloud-initconfiguration for automatic container orchestration and persistent volume mounting.Key additions:
cargo-cheffor optimized cachingcloud-inittemplate for automated Docker setup, volume mounting, and container deploymentMakefilewith targets for production Docker builds and Terraform stack managementdocker-compose.ymlto utilize the published GHCR imageTypes of changes
Checklist