Skip to content

[DevOps] Add GHCR publishing and DigitalOcean Terraform infrastructure#3

Merged
nxdun merged 29 commits intomainfrom
nadun/devops/core-infra
Mar 9, 2026
Merged

[DevOps] Add GHCR publishing and DigitalOcean Terraform infrastructure#3
nxdun merged 29 commits intomainfrom
nadun/devops/core-infra

Conversation

@nxdun
Copy link
Copy Markdown
Owner

@nxdun nxdun commented Feb 28, 2026

Description

Introduces DigitalOcean provisioning using Terraform. This enables seamless cloud deployment with cloud-init configuration for automatic container orchestration and persistent volume mounting.

Key additions:

  • Add GitHub Actions workflow to build and push Docker images to GHCR
  • Refactor Dockerfile to use a 5-stage Alpine-based build with cargo-chef for optimized caching
  • Add Terraform configuration to provision DigitalOcean Droplets and persistent block storage
  • Add cloud-init template for automated Docker setup, volume mounting, and container deployment
  • Update Makefile with targets for production Docker builds and Terraform stack management
  • Update docker-compose.yml to utilize the published GHCR image

Types of changes

  • Feature

Checklist

  • Updated ChangeLog
  • Implemented multi-stage Docker builds
  • Added Infrastructure as Code (IaC) with Terraform
  • Automated container publishing via GitHub Actions

@nxdun nxdun self-assigned this Feb 28, 2026
…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
@nxdun nxdun changed the title [DevOps] Core Deployment Infrastructure [DevOps] Add GHCR publishing and DigitalOcean Terraform infrastructure Mar 8, 2026
@nxdun nxdun marked this pull request as ready for review March 9, 2026 02:48
@nxdun nxdun requested a review from Copilot March 9, 2026 02:48
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

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 /health response with a cookies boolean 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
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

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

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).

Suggested change
deploy: lrelease ## Run release binary locally
deploy: release ## Build and run release binary locally

Copilot uses AI. Check for mistakes.
Comment on lines +9 to +15
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"
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

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

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).

Copilot uses AI. Check for mistakes.
$(Q)DOCKER_BUILDKIT=1 docker buildx build \
--builder $(BUILDER_NAME) \
--platform $(PLATFORMS) \
--platform linux/amd64 \
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
--platform linux/amd64 \
--platform $(PLATFORMS) \

Copilot uses AI. Check for mistakes.
Comment on lines +46 to +57
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
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

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

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).

Copilot uses AI. Check for mistakes.
Comment on lines +3 to +10
```mermaid
---
---
config:
theme: neo-dark
look: neo
layout: dagre
---
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Comment on lines +3 to +7
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
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
@nxdun nxdun merged commit e876dab into main Mar 9, 2026
1 check passed
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