Let's Talk →
← BACK TO WRITING

Nix flake for Codeberg

Recently, I decided to start migrating from Github to Codeberg to host some of my projects, starting with personal ones, like this blog. Which doesn't affect collaboration and I can experiment freely.

On top of that, I wanted to move from woile.dev to woile.eu. The .dev TLD is controlled by Google, while the .eu TLD is controlled by an NGO and it's more affordable. It's a tiny step towards de-googling.

The transition has been mostly smooth, as you can easily import a repo from Github using the "New migration" button. And, you can also configure the repo to automatically push to Github, to keep them in sync.

One difference though, is the approach Codeberg takes to CI: it demands efficiency

Understandably so, for costs ($$) and the environment. Where commercial platforms can waste resources to reduce friction, Codeberg cannot. So each time I tried to deploy this website, the runner was killed 😵

I was forced to audit this blog's build process.

My static site builder is sukr, a lightweight 35MB Rust binary, so I assumed the CI setup would be straightforward, especially since Codeberg Actions can reuse GitHub Actions workflows.

Oh boy, was I wrong…

It was a trial-and-error journey of reading docs and asking around, which ultimately boiled down to resolving three key issues.

Choosing a Runner

I started with codeberg-tiny, which is a 2GB RAM and 1 CPU, but it just didn't cut it. I kept getting:

context deadline exceeded

Unfortunately, nix is quite resource hungry, I tried --max-jobs 1 --cores 1 but still, it failed.

The solution was to move to codeberg-small with 4GB Ram and 2 CPU.

Cache Miss

The first few runs, the runner wouldn't even detect the cache, which naive me thought, was properly configured.

these 102 derivations will be built:
  /nix/store/5isaqg5lhx0zh42lkhz4ic51j4i2as5q-site.drv
  /nix/store/r5hx215xlhs5icvn1bd5fwa8sipjwam3-sukr-0.3.0.drv
  /nix/store/31wyw018lmmyg5z0q9kf5jcij05klazh-rustc-1.94.1-x86_64-unknown-linux-gnu.tar.gz
  etc...

After plenty of trial and error, I reached out online. Thanks to isabel, I learned that despite using gitignore filters to build the flake's package, untracked files or metadata in the .git directory were still altering the Nix store input path and busting the cache.

Using lib.fileset, to select exactly what I want to include, did the trick.

{
  packages.sukr = rustplatform.buildRustPackage {
    src = lib.fileset.toSource {
      root = ./.;
      fileset = lib.fileset.unions [
        ./Cargo.toml
        ./Cargo.lock
        ./.rustfmt.toml
        ./src
        ./queries
        ./patches
      ];
    };
    # etc...
  };
}

🛡️ Bonus Security Win

Using lib.fileset also acts as a security boundary. It prevents untracked local secrets, .env files, or git artifacts from unintentionally leaking into the world readable Nix store closure. I guess it's a "must adopt" for my radar.

Now, when using sukr on my blog, on the CI, I got a very nice looking:

this derivation will be built:
  /nix/store/izigi3alp1313qvsb300hyz78fihv7gx-site.drv

This means, that everything, except my site, had hit the cache.

Okay, better, but the pipeline was still failing.

Optimizing the Build

Despite building a single derivation, the 35MB sukr binary was still pulling a massive amount of build dependencies.

these 302 paths will be fetched (845.1 MiB download, 4.6 GiB unpacked):
  /nix/store/hr1ykvaysx4xbkcqp34cx8j4mbzrcgm2-rust-src-stable-2026-07-09
  /nix/store/8wpm7041lk1lrqwnyjxvbchky1kl7cdc-sukr-0.3.0
  /nix/store/kx2w7xgyas7hd54fc2lg1pp45351nc68-tree-sitter-0.25.10
  etc...

Wait, what? Why are rust-src and tree-sitter being pulled? I already built the binary.

After a long and enlightening investigation, it turns out that some references stay linked to the binary when building with rustplatform.buildRustPackage. I do not know why, but the rust toolchain used to build the binary was there.

$ nix path-info --recursive ./result
/nix/store/dxszpyw0pn7h1cxi1y6l33nmwk7f5knn-libunistring-1.4.2
/nix/store/clqknm3pgln8pxb6n7iamffk313nr91r-libidn2-2.3.8
/nix/store/hps7pxvcgq32x46yx5a2nx53js5j41vb-xgcc-15.2.0-libgcc
/nix/store/ias8xacs1h3jy7xgwi2awvim61k2ji6c-glibc-2.42-67
/nix/store/26x9wbicyw9bsb4rkp03vs51wa10nmis-cargo-stable-2026-07-09
/nix/store/2w2sp7vm072pmc2m3kmhbv1xvbfwmz96-clippy-preview-stable-2026-07-09
/nix/store/6jl87lvis8529lrmzk8mv4mp0rz6wc9c-rustc-docs-stable-2026-07-09
/nix/store/wlj7fzgbchbxhd6hplwm2dwl15gizcn5-gcc-15.2.0-libgcc
/nix/store/8lahnh9pn3lrrnhax5nk7ibvjcbjmnkm-gcc-15.2.0-lib
/nix/store/aaapdgy9ag0j4w8h12x1rg7p3gn1hxzf-llvm-bitcode-linker-preview-stable-2026-07-09
/nix/store/b2swxfi8srrbsafvh9iyyhd26mz9giwf-zlib-1.3.2
/nix/store/c6w6sfih2mbgnrz6vy47jfs5yslj15ir-llvm-tools-preview-stable-2026-07-09
/nix/store/v8llyqw71lygr2llhmcc8ya5bdlzq45v-bash-5.3p9
/nix/store/hr1ykvaysx4xbkcqp34cx8j4mbzrcgm2-rust-src-stable-2026-07-09
/nix/store/lzqzq8vswwjimm0na5ry1dq1vi57gvsn-rust-stable-2026-07-09
/nix/store/y1plvd789wnw0ilzg19lnfkz5qj6q4x0-reproducible-artifacts-stable-2026-07-09
/nix/store/nvfp28af4sm6ckm0nibpwapy748gxdzz-rust-mixed
/nix/store/a7h87n3smi0w2xkas65p2zm06k29nc5w-sukr-0.3.0

That's when I was hit by removeReferencesTo, which says:

This is useful for getting rid of dependencies that you know are not actually needed at runtime.

Using it to clean the references to the toolchain like this.

{
  packages.sukr = rustplatform.buildRustPackage {
    nativeBuildInputs = [ pkgs.removeReferencesTo ];
    postFixup = ''
      remove-references-to -t ${toolchain} $out/bin/sukr
    '';
  };
}

Finally, taking a look at the output again, it contains things which make more sense, like glibc.

$ nix path-info -r ./result
/nix/store/dxszpyw0pn7h1cxi1y6l33nmwk7f5knn-libunistring-1.4.2
/nix/store/clqknm3pgln8pxb6n7iamffk313nr91r-libidn2-2.3.8
/nix/store/hps7pxvcgq32x46yx5a2nx53js5j41vb-xgcc-15.2.0-libgcc
/nix/store/ias8xacs1h3jy7xgwi2awvim61k2ji6c-glibc-2.42-67
/nix/store/8wpm7041lk1lrqwnyjxvbchky1kl7cdc-sukr-0.3.0

After sukr was built and deployed to the cache. Running the blog gave us a beautiful:

these 48 paths will be fetched (133.2 MiB download, 538.9 MiB unpacked):

Which is well below what Codeberg asks.

And after a few, unrelated changes, we got the beautiful green checkmark.

CI running successfully

Conclusions

On one hand, I wonder if I really needed to "waste" 2 days fixing this. On the other, I'm no enterprise, and I don't need to optimize my time efficiently to make more money.

Just as I value Rust for preventing resource waste at the software level, I realized I should apply that same mindset to CI. Until now, I hadn't given it much thought, I was taking compute resources for granted.

In the end, I'm happy with the results. It was a fun learning experience, which also, ended up benefiting another project of mine.

Thanks for reading.

Written by a Human, Not by AI