Welcome to Chapter 18! Up to this point, we’ve built a powerful, flexible, and efficient Static Site Generator (SSG) in Rust. We’ve handled everything from content parsing and templating to component hydration and incremental builds. However, building the site locally is only half the battle. To truly make our SSG production-ready, we need a robust system for automatically building and deploying our static assets whenever content changes or new features are introduced.
In this chapter, we will focus on the crucial aspects of deployment and Continuous Integration/Continuous Deployment (CI/CD). We’ll explore various static hosting options suitable for our SSG’s output and then design and implement a CI/CD pipeline using GitHub Actions. This pipeline will automate the entire process: fetching the latest code, building our Rust SSG, running tests, generating the static website, and finally deploying it to a chosen hosting provider. By the end of this chapter, you will have a fully automated system that takes your SSG content from source control to a live website with minimal manual intervention, ensuring rapid updates and consistent deployments.
Planning & Design
Before diving into the implementation, let’s outline our deployment strategy and CI/CD workflow.
1. Deployment Targets for Static Sites
Our Rust SSG produces a directory full of static HTML, CSS, JavaScript, and asset files. This makes it ideal for deployment on various static hosting services. Here are some popular options and their characteristics:
- GitHub Pages: Excellent for projects hosted on GitHub. Free, simple to set up, and integrates seamlessly with GitHub Actions. It’s often sufficient for blogs, documentation, and personal websites.
- Netlify: A popular choice offering a generous free tier, global CDN, custom domains, HTTPS, and advanced features like serverless functions and form handling. It automatically detects SSGs and provides a smooth deployment experience.
- Vercel: Similar to Netlify, Vercel provides a fast, global CDN, automatic deployments, and a focus on developer experience. It’s particularly popular for React/Next.js and other JavaScript frameworks but works equally well for any static site.
- AWS S3 + CloudFront: For more control and enterprise-grade scalability, deploying to an S3 bucket and serving it through CloudFront (AWS’s CDN) is a powerful option. It requires more setup but offers maximum flexibility and integration with other AWS services.
- Google Cloud Storage + Cloud CDN: Google Cloud’s equivalent to S3 + CloudFront, offering similar benefits and control.
For this chapter, we will focus on GitHub Pages due to its tight integration with GitHub Actions and its simplicity for demonstrating automated deployment. The principles learned, however, will be easily transferable to other platforms.
2. CI/CD Workflow Design with GitHub Actions
Our CI/CD pipeline needs to perform a series of steps automatically whenever we push changes to our main branch. Here’s the proposed workflow:
- Trigger: The workflow should start on
pushevents to themainbranch. - Environment Setup: Set up a Rust environment on the runner.
- Build SSG: Compile our Rust SSG application.
- Run Tests: Execute unit and integration tests for our SSG. This ensures that changes haven’t introduced regressions.
- Generate Site: Run the compiled SSG executable to generate the static
publicdirectory. - Deploy: Publish the contents of the
publicdirectory to GitHub Pages.
Here’s a visual representation of our CI/CD pipeline using a Mermaid diagram:
Step-by-Step Implementation
We’ll implement this workflow using GitHub Actions.
a) Setup/Configuration
First, ensure your project is a Git repository hosted on GitHub. If not, initialize Git and push your code to a new GitHub repository.
We need to create a new YAML file in your project’s .github/workflows/ directory. This is where GitHub Actions workflows are defined.
File: .github/workflows/deploy.yml
# .github/workflows/deploy.yml
name: Deploy Static Site
on:
push:
branches:
- main # Trigger on pushes to the 'main' branch
env:
CARGO_TERM_COLOR: always # Always show colored output from Cargo
jobs:
build_and_deploy:
runs-on: ubuntu-latest # Use the latest Ubuntu runner
steps:
- name: Checkout repository
uses: actions/checkout@v4 # Action to checkout your repository code
- name: Setup Rust toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable # Use the stable Rust toolchain
profile: minimal
override: true
- name: Cache Cargo dependencies
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-
- name: Build SSG
run: cargo build --release # Build our SSG in release mode for performance
- name: Run tests
run: cargo test --verbose # Run all tests defined in our SSG project
- name: Generate static site
run: ./target/release/your_ssg_name build # Execute our SSG to generate the static files
# IMPORTANT: Replace 'your_ssg_name' with the actual binary name of your SSG
# For example, if your Cargo.toml has `name = "my_ssg"`, then it's `my_ssg`.
# Ensure your SSG's `build` command outputs to a `public` directory.
- name: Deploy to GitHub Pages
uses: peaceiris/actions-gh-pages@v4
if: github.ref == 'refs/heads/main' # Only deploy if the push is to the main branch
with:
github_token: ${{ secrets.GITHUB_TOKEN }} # Automatically provided by GitHub Actions
publish_dir: ./public # The directory containing your generated static site
cname: your-domain.com # OPTIONAL: If you're using a custom domain
# Replace 'your-domain.com' with your actual custom domain, or remove this line.
Explanation of the deploy.yml file:
name: A descriptive name for your workflow, which will appear in the GitHub Actions UI.on: Defines when the workflow should run. Here, it runs onpushevents to themainbranch.env: Sets environment variables available to all jobs in the workflow.CARGO_TERM_COLOR: alwaysensures Cargo output is colored, making logs easier to read.jobs: Workflows are composed of one or more jobs. We have a single job namedbuild_and_deploy.runs-on: Specifies the operating system for the runner.ubuntu-latestis a common choice.steps: A sequence of tasks to be executed in the job.Checkout repository: Usesactions/checkout@v4to clone your repository onto the runner.Setup Rust toolchain: Usesactions-rs/toolchain@v1to install the Rust compiler and Cargo. We specifystablefor the toolchain.Cache Cargo dependencies: This is a crucial optimization. It caches~/.cargo/registry,~/.cargo/git, and thetargetdirectory. This significantly speeds up subsequent builds by reusing downloaded crates and compiled artifacts. Thekeyuses a hash ofCargo.lockto bust the cache if dependencies change.Build SSG: Runscargo build --release. The--releaseflag compiles your SSG with optimizations, making it faster.Run tests: Executescargo test --verboseto run all your project’s tests. This is a critical CI step to ensure code quality.Generate static site: This step executes your compiled SSG binary. You MUST replaceyour_ssg_namewith the actual name of the executable yourCargo.tomlfile defines (e.g., ifname = "my_ssg"inCargo.toml, the binary will bemy_ssg). We assume yourbuildcommand generates files into apublicdirectory, which is a common convention for SSGs.Deploy to GitHub Pages: Usespeaceiris/actions-gh-pages@v4, a popular action for deploying to GitHub Pages.if: github.ref == 'refs/heads/main': Ensures deployment only happens when pushing to themainbranch.github_token: ${{ secrets.GITHUB_TOKEN }}: This is a special token automatically provided by GitHub Actions with permissions to deploy to GitHub Pages. You don’t need to create it.publish_dir: ./public: Tells the action which directory contains the static files to deploy.cname: (Optional) If you’re using a custom domain for your GitHub Pages site, specify it here. Otherwise, remove this line.
b) Core Implementation
The core implementation for this chapter is the deploy.yml file itself. We’ve just walked through its creation and explanation.
To make this functional, you need to ensure:
Your SSG’s
buildcommand: It must be accessible as./target/release/your_ssg_name buildand produce its output in a directory namedpublic(or whatever you specify forpublish_dir).- Example from previous chapters (simplified):
// src/main.rs (simplified for demonstration, assuming you have a `build` function) // ... (your existing SSG code) ... fn main() -> Result<(), Box<dyn std::error::Error>> { let args: Vec<String> = std::env::args().collect(); if args.len() > 1 && args[1] == "build" { // Ensure the output directory is 'public' let output_dir = PathBuf::from("public"); if output_dir.exists() { std::fs::remove_dir_all(&output_dir)?; } std::fs::create_dir_all(&output_dir)?; println!("Building site into: {}", output_dir.display()); // Your existing build logic here, e.g., // let content_tree = read_content_directory("content")?; // let templates = load_templates("templates")?; // let rendered_pages = render_content(&content_tree, &templates)?; // write_pages_to_disk(&rendered_pages, &output_dir)?; // copy_static_assets("static", &output_dir)?; println!("Site built successfully!"); Ok(()) } else { eprintln!("Usage: {} build", args[0]); Err("Invalid command".into()) } } - Verify your
Cargo.tomlnamefield:In this case, the command in the workflow would be# Cargo.toml [package] name = "my_rust_ssg" # This is 'your_ssg_name' version = "0.1.0" edition = "2021" # ... other dependencies ..../target/release/my_rust_ssg build.
- Example from previous chapters (simplified):
GitHub Pages Setup:
- Go to your GitHub repository settings.
- Navigate to the “Pages” section (usually under “Code and automation”).
- Under “Build and deployment”, select “GitHub Actions” as the source. This tells GitHub Pages to expect deployments from your workflow.
c) Testing This Component
To test the CI/CD pipeline:
- Commit and Push: Commit the
deploy.ymlfile to yourmainbranch.git add .github/workflows/deploy.yml git commit -m "feat: Add GitHub Actions for CI/CD and deployment" git push origin main - Monitor Workflow:
- Go to your GitHub repository.
- Click on the “Actions” tab.
- You should see your “Deploy Static Site” workflow running. Click on it to view the real-time logs for each step.
- Verify Deployment:
- Once the workflow completes successfully, go back to your repository settings, then “Pages”.
- GitHub will provide a URL for your deployed site (e.g.,
https://your-username.github.io/your-repo-name/). - Open this URL in your browser to verify that your static site is live and correctly rendered.
Production Considerations
While GitHub Pages is convenient, production deployments often require more robust considerations:
- Environment Variables and Secrets:
- If your SSG fetches data from external APIs during the build process (e.g., a headless CMS), you might need API keys. Never hardcode these in your workflow file or repository.
- Use GitHub Secrets for sensitive information. You can define secrets in your repository settings (
Settings -> Secrets and variables -> Actions). - Access them in your workflow like
${{ secrets.API_KEY }}. - Example (if your SSG needed an API key during build):
# ... inside your 'Generate static site' step ... env: MY_API_KEY: ${{ secrets.MY_API_KEY }} run: ./target/release/your_ssg_name build --api-key $MY_API_KEY
- Caching: We’ve already implemented Cargo dependency caching. For very large projects, you might also consider caching the
publicdirectory itself if only minor changes occur, though for static sites, regenerating the whole thing is often fast enough. - Rollbacks:
- GitHub Pages keeps a history of deployments. If a deployment is bad, you can revert to a previous successful deployment directly from the GitHub Pages settings UI.
- For other hosts like Netlify/Vercel, rollbacks are often a built-in feature.
- For S3/CloudFront, you’d typically manage versions of your S3 bucket content.
- Monitoring and Alerting:
- For critical production sites, you’d want external monitoring (e.g., UptimeRobot, New Relic, Datadog) to check site availability and performance.
- Integrate alerts (email, Slack, PagerDuty) if the site goes down or experiences errors.
- Build Time Optimization:
- Incremental Builds: Our SSG already supports incremental builds. Ensure your CI/CD pipeline leverages this by passing the appropriate flags to your SSG’s
buildcommand (e.g.,your_ssg_name build --incremental). This will rely on your SSG’s internal caching mechanisms. sccache: A cacher for Rust compilation. You can integratesccacheinto your GitHub Actions workflow for even faster Rust compilation times, especially for clean builds or when the Cargo cache misses.# ... after Setup Rust toolchain ... - name: Install sccache uses: mozilla-actions/sccache-action@v3 - name: Build SSG with sccache run: cargo build --release env: RUSTC_WRAPPER: sccache # Tell Cargo to use sccache
- Incremental Builds: Our SSG already supports incremental builds. Ensure your CI/CD pipeline leverages this by passing the appropriate flags to your SSG’s
- CDN Configuration: For maximum performance, ensure your static host uses a Content Delivery Network (CDN) and that caching headers are correctly set for optimal browser caching. GitHub Pages provides a CDN automatically.
Code Review Checkpoint
At this stage, you should have a deploy.yml file in your .github/workflows/ directory. This file defines a GitHub Actions workflow that:
- Triggers on pushes to the
mainbranch. - Sets up the Rust environment and caches dependencies.
- Builds your SSG executable in release mode.
- Runs all your SSG’s tests.
- Executes your SSG to generate the static website into the
publicdirectory. - Deploys the
publicdirectory to GitHub Pages.
This significantly streamlines the process of getting your SSG-generated content live, making content updates and feature releases much more efficient and reliable.
Common Issues & Solutions
- Workflow Fails During Rust Build/Test:
- Issue:
cargo build --releaseorcargo testfails. - Solution: Examine the workflow logs carefully. GitHub Actions provides detailed output. Look for Rust compiler errors, test failures, or missing dependencies. Often, it’s a syntax error, a failed test, or a missing
usestatement. Ensure your code compiles and tests pass locally before pushing. - Prevention: Always run
cargo build --releaseandcargo testlocally before committing and pushing.
- Issue:
- SSG Execution Fails or Output Directory is Empty:
- Issue: The
Generate static sitestep completes, but thepublicdirectory is empty, or the SSG binary cannot be found/executed. - Solution:
- Binary Name: Double-check that
./target/release/your_ssg_nameexactly matches thenamefield in yourCargo.toml. - Output Path: Verify that your SSG’s build command actually creates files in the
./publicdirectory. If it creates them elsewhere (e.g.,./dist), updatepublish_dirin thepeaceiris/actions-gh-pagesstep. - Permissions: Ensure your SSG has the necessary permissions to create/write to the output directory. This is usually not an issue on GitHub Actions runners, but good to check.
- Binary Name: Double-check that
- Debugging: Add
ls -la target/releaseandls -la publiccommands in your workflow after the SSG execution step to inspect the file system.
- Issue: The
- Deployment to GitHub Pages Fails (Permissions/Access):
- Issue: The
Deploy to GitHub Pagesstep fails with permission errors. - Solution:
- GitHub Token: Ensure
github_token: ${{ secrets.GITHUB_TOKEN }}is correctly specified. This token is usually sufficient. - GitHub Pages Source: Go to your repository settings -> Pages. Under “Build and deployment”, ensure “GitHub Actions” is selected as the source. This grants the workflow the necessary permissions.
- Branch Protection: If you have branch protection rules on
main, ensure the GitHub Actions bot has permissions to push to thegh-pagesbranch (whichpeaceiris/actions-gh-pagesuses internally).
- GitHub Token: Ensure
- Debugging: Check the logs from the
peaceiris/actions-gh-pagesstep for specific error messages.
- Issue: The
Testing & Verification
After implementing the CI/CD workflow:
- Trigger a new build: Make a minor change to a content file (e.g.,
content/index.md), commit, and push tomain. - Monitor Actions: Go to your repository’s “Actions” tab and watch the workflow progress.
- Verify Success: Ensure all steps in the
build_and_deployjob complete successfully (green checkmarks). - Check Deployed Site: Visit your GitHub Pages URL (e.g.,
https://your-username.github.io/your-repo-name/). Verify that the changes you made to the content file are reflected on the live site. - Test a failure: (Optional, for robustness) Introduce a deliberate error in your SSG code (e.g., a compile error) and push. Observe the workflow failing and the appropriate error messages in the logs. Then revert the change and push again.
This systematic testing ensures your automated deployment pipeline is robust and reliable.
Summary & Next Steps
In this chapter, we successfully established a robust CI/CD pipeline for our Rust SSG using GitHub Actions. We configured the workflow to automatically build, test, and deploy our static website to GitHub Pages upon every push to the main branch. We covered essential production considerations like caching, secrets management, and build time optimizations, ensuring our SSG is ready for real-world use. This automation significantly reduces manual effort, speeds up content delivery, and improves the overall reliability of our content platform.
With our SSG now fully capable of automated deployment, we have a complete, production-ready system. In the next chapters, we will leverage this foundation to build out real-world example projects. These projects will demonstrate the power and flexibility of our Rust SSG, applying all the concepts and features we’ve developed throughout this guide.