fzakaria/guixpkgs

Guix Packages collection built via Nix

Nix

56

81 commits

updated Oct 4, 2026

See the code

README

GuixPkgs Logo

GuixPkgs

GuixPkgs translates selected GNU Guix package graphs into checked-in Nix expressions. Nix evaluates them without Import From Derivation (IFD) or a running Guix daemon.

Motivation

Guix and Nix share the exact same underlying deployment model, but they are built on different languages (Guile Scheme vs. the Nix expression language). While it is technically possible to evaluate Guix packages within Nix using IFD (Import From Derivation), IFD is notoriously slow, breaks evaluation caching, and is forbidden in pure evaluation modes (such as standard Nix flakes).

We want to allow users to transparently mix and match nixpkgs and Guix packages within the same Nix flake, retaining fast evaluation and full reproducibility.

Goal

The goal of this repository is to act as a direct, automated translation layer:

  1. Translate the dependency closures of selected Guix packages at a given commit into a deduplicated set of .nix files.
  2. Provide a standard Nixpkgs-like interface (packages.x86_64-linux.<package>) via a flake, mapping names to translated packages wrapped with Guix-generated runtime environments.
  3. Automatically sync and translate the upstream Guix tree on a recurring schedule (e.g. via GitHub Actions), recording the exact Guix state in guix-metadata.json.

How it Works

GuixPkgs leverages guix-transfer to perform a one-time conversion per Guix commit. The resulting repository tree looks like this:

guixpkgs/
├── flake.nix             # The entry point exposing the Guix packages as Nix outputs
├── guix-metadata.json    # Tracks the Guix channel, commit, and sync timestamp
├── .github/
│   └── workflows/
│       └── sync.yml      # GitHub Action to periodically sync with upstream Guix
└── pkgs/
    ├── by-name/          # Human-readable entry points wrapping Guix packages
    │   ├── h/hello.nix   # e.g., `{ pkgs }: pkgs.callPackage ../../wrap-guix-package.nix { … }`
    │   └── z/zile.nix
    ├── store/            # The deduplicated translated Nix derivations, each a
    │   ├── b6x8v...-hello.nix             # standalone `builtins.derivation { … }`
    │   ├── c7y9w...-hello-runtime-env.nix # the package's generated `etc/profile`
    │   └── ...
    ├── sources/          # Local files (patches, builder scripts) referenced by the derivations
    └── wrap-guix-package.nix # Generic runtime wrapper for translated packages

Each store/*.nix file is a self-contained builtins.derivation that references its dependencies directly via (import ../store/<file>.nix).<output>. Public by-name entries don't expose that raw derivation; instead each is a small { pkgs } function that wraps the translated package and sources a companion *-runtime-env derivation's etc/profile before running any program. That runtime environment is generated by Guix's own search-path machinery over the package and its target runtime inputs, so tools work outside a Guix profile — without unioning those inputs into the visible package output.

The wrapper keeps both halves reachable through passthru: pkg.unwrapped is the raw translated main output and pkg.runtimeEnv is the generated profile. Packages that can serve as login shells also expose a package-relative pkg.shellPath; Bash currently reports /bin/bash. Some packages need small, Nix-specific build adjustments; those are applied during translation by guix-transfer, not in this repo — see Patching packages.

Syncing Manually

You can manually trigger the translation of Guix derivations into Nix expressions at any time. The flake exposes an app to run the synchronization process:

# Note: This requires `guix` to be installed and the Guix daemon running.
nix run .#sync

Package specifications are listed in %package-names in get-all-derivations.scm. Guix variables that are not package specifications belong in %aliased-packages, which preserves their requested public names.

After the sync completes, add the generated package tree and metadata so Nix can evaluate them:

git add pkgs/ guix-metadata.json

Binary Cache

To avoid building Guix packages from source, you can use the provided Cachix binary cache. You can configure this by adding the following nixConfig to your flake.nix:

  nixConfig = {
    extra-substituters = [ "https://guixpkgs.cachix.org" ];
    extra-trusted-public-keys = [ "guixpkgs.cachix.org-1:rM4xwCs5NUy+FcCKkiWP/CmRaSVxxDPaKWZvM1bRopg=" ];
  };

Alternatively, you can temporarily use it via the command line:

nix build .#hello \
  --extra-substituters https://guixpkgs.cachix.org \
  --extra-trusted-public-keys guixpkgs.cachix.org-1:rM4xwCs5NUy+FcCKkiWP/CmRaSVxxDPaKWZvM1bRopg=

Example Usage

Because GuixPkgs translates Guix packages into pure Nix expressions, they become standard Nix derivations (technically, they evaluate to an attribute set created by builtins.derivation { ... }).

In your own projects, you can mix and match nixpkgs and guixpkgs seamlessly via flakes:

{
  description = "A project mixing Nix and Guix";

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
    guixpkgs.url = "github:fzakaria/guixpkgs";
  };

  outputs = { self, nixpkgs, guixpkgs }:
  let
    system = "x86_64-linux";
    pkgs = nixpkgs.legacyPackages.${system};
    guixPkgs = guixpkgs.packages.${system};
  in {
    devShells.${system}.default = pkgs.mkShell {
      buildInputs = [
        pkgs.git                   # From Nixpkgs
        guixPkgs.hello             # From Guix (via GuixPkgs)
      ];
    };
  };
}

Run standard Nix commands against Guix packages effortlessly:

[!NOTE] Building Guix packages from source within Nix can sometimes fail during bootstrap (e.g., chmod: Operation not permitted). This is often because Guix's bootstrap scripts try to restore SETGID bits, which the Nix sandbox strictly blocks. To successfully build from source, you need to disable syscall filtering.

[!IMPORTANT] Building from source also needs /bin/sh removed from the Nix sandbox (--option sandbox-paths ''), otherwise some packages build differently than they do under guix-daemon. See Sandbox parity below. Prefer this over --option sandbox false, which exposes the host's /bin/sh and the rest of the host filesystem.

# Build the GNU Hello package from Guix
nix build .#hello --option filter-syscalls false --option sandbox-paths ''

# Drop into a shell with Zile from Guix
nix shell .#zile --option filter-syscalls false --option sandbox-paths ''

Sandbox parity

Substitutes from the binary cache are unaffected by this section — it only matters when you build from source.

A translated derivation is a faithful copy of Guix's, but the daemon running it is not. guix-daemon's container has no /bin at all, while Nix's sandbox bind-mounts a /bin/sh; the sandbox-paths default is compiled into the Nix binary, and nixpkgs builds Nix with -Dsandbox-shell=<busybox>/bin/busybox, so on a typical install it is present:

nix config show | grep sandbox-paths
# sandbox-paths = /bin/sh=/nix/store/…-busybox-1.37.0/bin/busybox …

Guix packages are built on the assumption that no /bin/sh exists, which is why Guix patches shebangs so aggressively. A build step that quietly depends on one takes a different path under Nix, and the difference cannot be expressed in the derivation — so it has to be a build-time option.

Using the overlay

Rather than reaching into guixpkgs.packages.<system>, you can fold GuixPkgs into your own nixpkgs instance with an overlay. This is a consumer-facing overlay and has nothing to do with Why not a Nix overlay? below, which is about how packages are patched inside this repository.

overlays.default — Guix beside Nixpkgs

The default overlay is purely additive: it puts the whole translated set under pkgs.guixPackages and changes nothing else. Nixpkgs attributes keep their Nixpkgs meaning.

{
  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
    guixpkgs.url = "github:fzakaria/guixpkgs";
  };

  outputs = { self, nixpkgs, guixpkgs }:
  let
    system = "x86_64-linux";
    pkgs = import nixpkgs {
      inherit system;
      overlays = [ guixpkgs.overlays.default ];
    };
  in {
    devShells.${system}.default = pkgs.mkShell {
      buildInputs = [
        pkgs.git                   # Nixpkgs
        pkgs.hello                 # Nixpkgs' hello
        pkgs.guixPackages.hello    # Guix' hello
      ];
    };
  };
}

lib.mkOverlay — let Guix win for chosen names

To have pkgs.<name> itself resolve to the Guix package, build an overlay with guixpkgs.lib.mkOverlay. It still adds pkgs.guixPackages; only says which names are additionally shadowed at the top level.

overlays = [
  # `pkgs.hello` and `pkgs.tmux` are now Guix builds.
  (guixpkgs.lib.mkOverlay { only = [ "hello" "tmux" ]; })
];

The two package sets often spell the same program differently. Pass an attribute set — nixpkgs attribute = "guix package" — when they disagree:

overlays = [
  (guixpkgs.lib.mkOverlay {
    only = {
      gnused = "sed";           # Guix calls it `sed`
      nss-cacert = "nss-certs";
    };
  })
];

only = null shadows every name GuixPkgs provides. Read the next section before you try it.

A name in only that GuixPkgs does not package is an evaluation error rather than a silent no-op.

On systems other than x86_64-linux there are no translated packages to hand out, so the overlay stays out of the way instead of breaking the package set: every shadowed attribute keeps its Nixpkgs value, and reading pkgs.guixPackages.<name> fails with an error naming the one supported system.

Both overlays produce the same derivations as guixpkgs.packages.<system>, so the binary cache still applies.

What replacement can and cannot do

Shadowing works for leaf packages — the ones you install and run yourself. It does not work for packages Nixpkgs itself builds against, and only = null is a curiosity rather than a way to run Nixpkgs on Guix.

A translated Guix package is a plain derivation wrapped with symlinkJoin. It has none of the conventions Nixpkgs expressions assume of their dependencies: no split outputs, no override/overrideAttrs, no meta, no passthru beyond what the wrapper adds.

Patching packages

pkgs/store, pkgs/sources, and pkgs/by-name are generated and replaced on every sync. Do not edit them by hand. Some packages need small, Nix-specific adjustments because the Guix and Nix build sandboxes differ.

All such fixes are applied at translation time, in guix-transfer — never as a Nix overlay in this repo.

Why not a Nix overlay?

Guix bakes absolute dependency store paths into each package's builder script (the Guile %build-inputs):

("perl" . "/nix/store/h0gbcdg…-perl-boot0-5.36.0")

Those builder scripts are frozen source files. If you change a package's derivation in a way that alters its output hash (disabling tests, rewriting the builder, dropping an attribute), every consumer's builder still points at the old path — so the dependency vanishes from the consumer's PATH at build time (classic symptom: configure: error: perl not found). A Nix overlay can't fix this because it can't rewrite the frozen builders. The change has to happen where builders and dependency edges are emitted together: during translation.

How it's done

guix-transfer applies these fixes before any path is hashed, so the modified build and all downstream references stay consistent:

ProblemHandled by
Bootstrap test suites fail under Nix's sandboxguix-transfer --disable-tests rewrites #:tests? #t → #:tests? #f in every *-builder script. The nix run .#sync app passes this flag.
An installer applies setuid or setgid modes to a Nix store outputguix-transfer disables those modes beside the builder's existing install-time ownership guard before paths are hashed.
disallowedReferences (etc.) point at an untranslated /gnu/store path, which Nix rejects as an illegal reference specifierguix-transfer filters invalid specifiers out of the allowed/disallowed References/Requisites attributes.

If a new package needs a tweak, add it to guix-transfer (e.g. a builder substitution applied in stage_source) and re-sync — there is deliberately no overlay layer to reach for.

fzakaria/guixpkgs

Guix Packages collection built via Nix

Nix

56

81 commits

updated Oct 4, 2026

See the code

README

GuixPkgs Logo

GuixPkgs

GuixPkgs translates selected GNU Guix package graphs into checked-in Nix expressions. Nix evaluates them without Import From Derivation (IFD) or a running Guix daemon.

Motivation

Guix and Nix share the exact same underlying deployment model, but they are built on different languages (Guile Scheme vs. the Nix expression language). While it is technically possible to evaluate Guix packages within Nix using IFD (Import From Derivation), IFD is notoriously slow, breaks evaluation caching, and is forbidden in pure evaluation modes (such as standard Nix flakes).

We want to allow users to transparently mix and match nixpkgs and Guix packages within the same Nix flake, retaining fast evaluation and full reproducibility.

Goal

The goal of this repository is to act as a direct, automated translation layer:

  1. Translate the dependency closures of selected Guix packages at a given commit into a deduplicated set of .nix files.
  2. Provide a standard Nixpkgs-like interface (packages.x86_64-linux.<package>) via a flake, mapping names to translated packages wrapped with Guix-generated runtime environments.
  3. Automatically sync and translate the upstream Guix tree on a recurring schedule (e.g. via GitHub Actions), recording the exact Guix state in guix-metadata.json.

How it Works

GuixPkgs leverages guix-transfer to perform a one-time conversion per Guix commit. The resulting repository tree looks like this:

guixpkgs/
├── flake.nix             # The entry point exposing the Guix packages as Nix outputs
├── guix-metadata.json    # Tracks the Guix channel, commit, and sync timestamp
├── .github/
│   └── workflows/
│       └── sync.yml      # GitHub Action to periodically sync with upstream Guix
└── pkgs/
    ├── by-name/          # Human-readable entry points wrapping Guix packages
    │   ├── h/hello.nix   # e.g., `{ pkgs }: pkgs.callPackage ../../wrap-guix-package.nix { … }`
    │   └── z/zile.nix
    ├── store/            # The deduplicated translated Nix derivations, each a
    │   ├── b6x8v...-hello.nix             # standalone `builtins.derivation { … }`
    │   ├── c7y9w...-hello-runtime-env.nix # the package's generated `etc/profile`
    │   └── ...
    ├── sources/          # Local files (patches, builder scripts) referenced by the derivations
    └── wrap-guix-package.nix # Generic runtime wrapper for translated packages

Each store/*.nix file is a self-contained builtins.derivation that references its dependencies directly via (import ../store/<file>.nix).<output>. Public by-name entries don't expose that raw derivation; instead each is a small { pkgs } function that wraps the translated package and sources a companion *-runtime-env derivation's etc/profile before running any program. That runtime environment is generated by Guix's own search-path machinery over the package and its target runtime inputs, so tools work outside a Guix profile — without unioning those inputs into the visible package output.

The wrapper keeps both halves reachable through passthru: pkg.unwrapped is the raw translated main output and pkg.runtimeEnv is the generated profile. Packages that can serve as login shells also expose a package-relative pkg.shellPath; Bash currently reports /bin/bash. Some packages need small, Nix-specific build adjustments; those are applied during translation by guix-transfer, not in this repo — see Patching packages.

Syncing Manually

You can manually trigger the translation of Guix derivations into Nix expressions at any time. The flake exposes an app to run the synchronization process:

# Note: This requires `guix` to be installed and the Guix daemon running.
nix run .#sync

Package specifications are listed in %package-names in get-all-derivations.scm. Guix variables that are not package specifications belong in %aliased-packages, which preserves their requested public names.

After the sync completes, add the generated package tree and metadata so Nix can evaluate them:

git add pkgs/ guix-metadata.json

Binary Cache

To avoid building Guix packages from source, you can use the provided Cachix binary cache. You can configure this by adding the following nixConfig to your flake.nix:

  nixConfig = {
    extra-substituters = [ "https://guixpkgs.cachix.org" ];
    extra-trusted-public-keys = [ "guixpkgs.cachix.org-1:rM4xwCs5NUy+FcCKkiWP/CmRaSVxxDPaKWZvM1bRopg=" ];
  };

Alternatively, you can temporarily use it via the command line:

nix build .#hello \
  --extra-substituters https://guixpkgs.cachix.org \
  --extra-trusted-public-keys guixpkgs.cachix.org-1:rM4xwCs5NUy+FcCKkiWP/CmRaSVxxDPaKWZvM1bRopg=

Example Usage

Because GuixPkgs translates Guix packages into pure Nix expressions, they become standard Nix derivations (technically, they evaluate to an attribute set created by builtins.derivation { ... }).

In your own projects, you can mix and match nixpkgs and guixpkgs seamlessly via flakes:

{
  description = "A project mixing Nix and Guix";

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
    guixpkgs.url = "github:fzakaria/guixpkgs";
  };

  outputs = { self, nixpkgs, guixpkgs }:
  let
    system = "x86_64-linux";
    pkgs = nixpkgs.legacyPackages.${system};
    guixPkgs = guixpkgs.packages.${system};
  in {
    devShells.${system}.default = pkgs.mkShell {
      buildInputs = [
        pkgs.git                   # From Nixpkgs
        guixPkgs.hello             # From Guix (via GuixPkgs)
      ];
    };
  };
}

Run standard Nix commands against Guix packages effortlessly:

[!NOTE] Building Guix packages from source within Nix can sometimes fail during bootstrap (e.g., chmod: Operation not permitted). This is often because Guix's bootstrap scripts try to restore SETGID bits, which the Nix sandbox strictly blocks. To successfully build from source, you need to disable syscall filtering.

[!IMPORTANT] Building from source also needs /bin/sh removed from the Nix sandbox (--option sandbox-paths ''), otherwise some packages build differently than they do under guix-daemon. See Sandbox parity below. Prefer this over --option sandbox false, which exposes the host's /bin/sh and the rest of the host filesystem.

# Build the GNU Hello package from Guix
nix build .#hello --option filter-syscalls false --option sandbox-paths ''

# Drop into a shell with Zile from Guix
nix shell .#zile --option filter-syscalls false --option sandbox-paths ''

Sandbox parity

Substitutes from the binary cache are unaffected by this section — it only matters when you build from source.

A translated derivation is a faithful copy of Guix's, but the daemon running it is not. guix-daemon's container has no /bin at all, while Nix's sandbox bind-mounts a /bin/sh; the sandbox-paths default is compiled into the Nix binary, and nixpkgs builds Nix with -Dsandbox-shell=<busybox>/bin/busybox, so on a typical install it is present:

nix config show | grep sandbox-paths
# sandbox-paths = /bin/sh=/nix/store/…-busybox-1.37.0/bin/busybox …

Guix packages are built on the assumption that no /bin/sh exists, which is why Guix patches shebangs so aggressively. A build step that quietly depends on one takes a different path under Nix, and the difference cannot be expressed in the derivation — so it has to be a build-time option.

Using the overlay

Rather than reaching into guixpkgs.packages.<system>, you can fold GuixPkgs into your own nixpkgs instance with an overlay. This is a consumer-facing overlay and has nothing to do with Why not a Nix overlay? below, which is about how packages are patched inside this repository.

overlays.default — Guix beside Nixpkgs

The default overlay is purely additive: it puts the whole translated set under pkgs.guixPackages and changes nothing else. Nixpkgs attributes keep their Nixpkgs meaning.

{
  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
    guixpkgs.url = "github:fzakaria/guixpkgs";
  };

  outputs = { self, nixpkgs, guixpkgs }:
  let
    system = "x86_64-linux";
    pkgs = import nixpkgs {
      inherit system;
      overlays = [ guixpkgs.overlays.default ];
    };
  in {
    devShells.${system}.default = pkgs.mkShell {
      buildInputs = [
        pkgs.git                   # Nixpkgs
        pkgs.hello                 # Nixpkgs' hello
        pkgs.guixPackages.hello    # Guix' hello
      ];
    };
  };
}

lib.mkOverlay — let Guix win for chosen names

To have pkgs.<name> itself resolve to the Guix package, build an overlay with guixpkgs.lib.mkOverlay. It still adds pkgs.guixPackages; only says which names are additionally shadowed at the top level.

overlays = [
  # `pkgs.hello` and `pkgs.tmux` are now Guix builds.
  (guixpkgs.lib.mkOverlay { only = [ "hello" "tmux" ]; })
];

The two package sets often spell the same program differently. Pass an attribute set — nixpkgs attribute = "guix package" — when they disagree:

overlays = [
  (guixpkgs.lib.mkOverlay {
    only = {
      gnused = "sed";           # Guix calls it `sed`
      nss-cacert = "nss-certs";
    };
  })
];

only = null shadows every name GuixPkgs provides. Read the next section before you try it.

A name in only that GuixPkgs does not package is an evaluation error rather than a silent no-op.

On systems other than x86_64-linux there are no translated packages to hand out, so the overlay stays out of the way instead of breaking the package set: every shadowed attribute keeps its Nixpkgs value, and reading pkgs.guixPackages.<name> fails with an error naming the one supported system.

Both overlays produce the same derivations as guixpkgs.packages.<system>, so the binary cache still applies.

What replacement can and cannot do

Shadowing works for leaf packages — the ones you install and run yourself. It does not work for packages Nixpkgs itself builds against, and only = null is a curiosity rather than a way to run Nixpkgs on Guix.

A translated Guix package is a plain derivation wrapped with symlinkJoin. It has none of the conventions Nixpkgs expressions assume of their dependencies: no split outputs, no override/overrideAttrs, no meta, no passthru beyond what the wrapper adds.

Patching packages

pkgs/store, pkgs/sources, and pkgs/by-name are generated and replaced on every sync. Do not edit them by hand. Some packages need small, Nix-specific adjustments because the Guix and Nix build sandboxes differ.

All such fixes are applied at translation time, in guix-transfer — never as a Nix overlay in this repo.

Why not a Nix overlay?

Guix bakes absolute dependency store paths into each package's builder script (the Guile %build-inputs):

("perl" . "/nix/store/h0gbcdg…-perl-boot0-5.36.0")

Those builder scripts are frozen source files. If you change a package's derivation in a way that alters its output hash (disabling tests, rewriting the builder, dropping an attribute), every consumer's builder still points at the old path — so the dependency vanishes from the consumer's PATH at build time (classic symptom: configure: error: perl not found). A Nix overlay can't fix this because it can't rewrite the frozen builders. The change has to happen where builders and dependency edges are emitted together: during translation.

How it's done

guix-transfer applies these fixes before any path is hashed, so the modified build and all downstream references stay consistent:

ProblemHandled by
Bootstrap test suites fail under Nix's sandboxguix-transfer --disable-tests rewrites #:tests? #t → #:tests? #f in every *-builder script. The nix run .#sync app passes this flag.
An installer applies setuid or setgid modes to a Nix store outputguix-transfer disables those modes beside the builder's existing install-time ownership guard before paths are hashed.
disallowedReferences (etc.) point at an untranslated /gnu/store path, which Nix rejects as an illegal reference specifierguix-transfer filters invalid specifiers out of the allowed/disallowed References/Requisites attributes.

If a new package needs a tweak, add it to guix-transfer (e.g. a builder substitution applied in stage_source) and re-sync — there is deliberately no overlay layer to reach for.

Significant stargazers

Eli

49 followers · starred Aug 2026

Gabriel Nützi

72 followers · starred Sep 2026

Winter

259 followers · starred Jun 2026

jacobi petrucciani

118 followers · starred Jun 2026