Why Your Rust Project Deserves a Real CI/CD?
\n\nImagine: you're writing in Rust, the compiler is your best friend, the borrow checker keeps you on your toes, and the code runs like lightning. But when it comes to deployment, the magic ends. Running tests manually, building on your local machine, uploading the binary to a server via SCP — that's a road to nowhere. Especially when the project grows and a second developer joins the team.
\n\nCI/CD (Continuous Integration / Continuous Deployment) is not just a buzzword. It's a system that takes over the routine: run tests, check code style, build a release version, and send it where it needs to work. For Rust, with its emphasis on reliability and performance, build automation is a natural extension of the language's philosophy.
\n\nIn this article, we'll set up a CI/CD pipeline for a Rust application from scratch using GitHub Actions. You'll learn how to organize testing, linting, building binaries for different platforms, and deployment. All with real code examples you can copy into your project.
\n\nWhat Will We Automate?
\n\nBefore writing code, let's define our goals. Our pipeline will do four things:
\n\n- \n
- Linting and Formatting — to ensure the code meets Rust standards and contains no common errors. \n
- Testing — running all unit tests and, if any, integration tests. \n
- Building the Binary — compiling an optimized (release) version. \n
- Deployment — uploading the built artifact to a server or to GitHub Releases. \n
All of this will happen automatically on every push to the main branch or when a pull request is created. Sounds like magic? It's actually just YAML files and a couple of Cargo commands.
Step 1: Project Preparation
\n\nFor the example, we'll take a simple web server using Actix-web. But the pipeline is universal: it's suitable for any Rust project, be it a CLI utility, library, or microservice.
\n\nLet's create a new project:
\n\ncargo new rust-ci-cd-demo --bin\ncd rust-ci-cd-demo\n\nAdd dependencies to Cargo.toml:
[dependencies]\nactix-web = "4"\nserde = { version = "1", features = ["derive"] }\n\nAnd the simplest code in src/main.rs:
use actix_web::{get, App, HttpResponse, HttpServer, Responder};\n\n#[get("/")]\nasync fn hello() -> impl Responder {\n HttpResponse::Ok().body("Hello from Rust CI/CD!")\n}\n\n#[actix_web::main]\nasync fn main() -> std::io::Result<()> {\n HttpServer::new(|| {\n App::new().service(hello)\n })\n .bind(("0.0.0.0", 8080))?\n .run()\n .await\n}\n\nAdd a simple test in src/main.rs (at the bottom of the file):
#[cfg(test)]\nmod tests {\n use actix_web::{test, App, web};\n use crate::hello;\n\n #[actix_web::test]\n async fn test_hello_endpoint() {\n let app = test::init_service(App::new().service(hello)).await;\n let req = test::TestRequest::get().uri("/").to_request();\n let resp = test::call_service(&app, req).await;\n assert!(resp.status().is_success());\n }\n}\n\nEverything is ready. Now for the interesting part — setting up CI/CD.
\n\nStep 2: Creating a GitHub Actions Workflow
\n\nGitHub Actions is a built-in CI/CD tool for GitHub. It works based on YAML files located in the .github/workflows/ directory. Let's create it:
mkdir -p .github/workflows\ntouch .github/workflows/ci.yml\n\nOpen ci.yml and start writing the pipeline. Let's call it "Rust CI/CD Pipeline".
Basic Trigger and Environment
\n\nname: Rust CI/CD Pipeline\n\non:\n push:\n branches: [ main ]\n pull_request:\n branches: [ main ]\n\nenv:\n CARGO_TERM_CO