Simplifying Debian Packaging for Rust: A Step-by-Step Guide for Rust Developers
In the rapidly evolving world of software development, efficiency and simplicity are key. Packaging your applications for Debian-based systems can be a crucial step for those working with Rust, a powerful language known for its safety and performance. Today, I’m excited to guide you through converting your Rust package into a Debian (.deb) package that can later be installed on Debian, Ubuntu, Linux Mint, Pop_OS, and other Debian-based distros. This tutorial is designed to be straightforward, quick, and easy to follow.
Starting with a Basic Rust Project
Our journey begins with a simple “Hello World” project in Rust. This project is created using Cargo, the Rust package manager, and features a clean Cargo.toml
file with no dependencies:
$ cargo new my-app --bin
$ tree
.
|-- Cargo.toml
`-- src
`-- main.rs
$ cat Cargo.toml
[package]
name = "my-app"
version = "0.1.0"
edition = "2021"
[dependencies]
Preparing Your Project for Packaging
Before diving into the packaging process, it's essential to ensure that your project is functional and you are on a Debian-based system so that you can access Debian packaging-specific commands. If you are not on a Debian-based system you can simply open your project in a docker container and build it there:
docker run -it -v <path-to-your-project>:<where-to-mount-in-docker> debian:latest
This is the exact command I ran, it mounts my project directory in /app
directory of the docker container:
docker run -it -v ~/Documents/Programming/debpkging/my-app:/app debian:latest
Once confirmed, you can begin the transformation into a Debian package. This first involves installing a Cargo plugin called cargo-deb
:
cargo install cargo-deb
Configuring Cargo.toml
The next step is crucial — updating the Cargo.toml
file with the necessary metadata for your Debian package. This metadata includes details like the maintainer, copyright, etc.:
[package]
name = "my-app"
version = "0.1.0"
edition = "2021"
[dependencies]
[package.metadata.deb]
maintainer = "Jan Bronicki <janbronicki@gmail.com>"
copyright = "2024, Jan Bronicki <janbronicki@gmail.com>"
extended-description = """A simple hello world program!"""
depends = "$auto"
section = "utility"
priority = "optional"
assets = [
["target/release/my-app", "usr/bin/", "755"],
]
You can find out more about available fields here.
Specifying Binary Output in Cargo.toml
The most critical aspect of this process is instructing cargo-deb
on handling the output binary. When you build your Rust project using Cargo, it outputs to the target/
directory, with subdirectories for release and debug versions. In our case, working on a simple binary project, the output is my-app
binary.
Here’s where the specifics come in. You need to tell cargo-deb to take this binary and place it in a specific directory upon installation of the package. This is done by specifying a path in the metadata. For example, you might want your binary to reside in /usr/bin/
after installation. However, in the configuration, you don’t include the ‘root’ ( /
) part of the path. This might seem confusing at first, but it’s a crucial step in ensuring your binary is correctly located.
assets = [
["target/release/my-app", "usr/bin/", "755"],
]
Here I would like to mention that where you should put your binaries has some logic behind it, for the sake of the simplicity of this guide we are going to put them in /usr/bin/
but you should research beforehand where your binaries should be put on a production system, here is a great article that outlines that.
Building Your .deb Package
With your project configured, the next step is to run cargo deb
to build the .deb package:
$ cargo deb
warning: description field is missing in Cargo.toml
warning: license field is missing in Cargo.toml
Finished release [optimized] target(s) in 0.00s
/app/target/debian/my-app_0.1.0-1_arm64.deb
Installing and Testing the Package
After building the package, it’s time to install and test it. This involves using the dpkg -i
command to install the package and then execute it to ensure it works correctly. After building the package you should see the path to the .deb
file as part of the output itself, you can simply use that path to now install the package with dpkg
in my case it's going to be:
$ dpkg -i ./target/debian/my-app_0.1.0-1_arm64.deb
Selecting previously unselected package my-app.
(Reading database ... 16606 files and directories currently installed.)
Preparing to unpack .../my-app_0.1.0-1_arm64.deb ...
Unpacking my-app (0.1.0-1) ...
Setting up my-app (0.1.0-1) ...
Verifying and Uninstalling the Package
Finally, verify that the package is correctly installed and learn how to uninstall it. This is crucial for maintaining the integrity of your system.
After installing the package the my-app
binary should be visible and executable from any place on the system:
$ my-app
Hello, world!
You can also check that the system is aware that this is an installed package by running dpkg --list
:
$ dpkg --list | grep my-app
ii my-app 0.1.0-1 arm64 [generated from Rust crate my-app]
We can also see that the my-app
binary resides in the specified directory in Cargo.toml
file:
$ ls /usr/bin/ | grep my-app
my-app
If you wish to uninstall the package you can use the dpkg
command again:
$ dpkg -r my-app
(Reading database ... 16609 files and directories currently installed.)
Removing my-app (0.1.0-1) ...
This will uninstall the package from the local package registry as well as delete all files belonging to the package, so you can rest assured that you will not leave any trash behind:
$ my-app
bash: /usr/bin/my-app: No such file or directory
$ ls /usr/bin/ | grep my-app
$ # As you can see above the my-app binary was not found
Conclusion
Packaging a Rust application for Debian systems is not only limited to Rust; this technique can be adapted for various programming languages. It’s a valuable skill that enhances your development workflow, allowing you to distribute your applications more efficiently.
What’s next?
The journey doesn’t stop here. This approach is not just limited to simple applications but can also be extended to libraries and more significant projects. Integration into your CI/CD pipeline can automate the packaging process, making distribution and version management a breeze. Whether you add your package to the official Debian repositories or host your own Personal Package Archive (PPA), these steps provide a foundation for broader distribution and easier access for users.
I hope you found this guide helpful. Remember to share, and follow for more content like this!
This article is based on this video: