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.
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.
The goal of this repository is to act as a direct, automated translation layer:
.nix files.packages.x86_64-linux.<package>) via a flake, mapping names to translated packages wrapped with Guix-generated runtime environments.guix-metadata.json.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.
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
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=
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 restoreSETGIDbits, which the Nix sandbox strictly blocks. To successfully build from source, you need to disable syscall filtering.
[!IMPORTANT] Building from source also needs
/bin/shremoved from the Nix sandbox (--option sandbox-paths ''), otherwise some packages build differently than they do underguix-daemon. See Sandbox parity below. Prefer this over--option sandbox false, which exposes the host's/bin/shand 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 ''
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.
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 NixpkgsThe 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 namesTo 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.
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.
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.
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.
guix-transfer applies these fixes before any path is hashed, so the modified build and all downstream references stay consistent:
| Problem | Handled by |
|---|---|
| Bootstrap test suites fail under Nix's sandbox | guix-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 output | guix-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 specifier | guix-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.
49 followers · starred Aug 2026
72 followers · starred Sep 2026
259 followers · starred Jun 2026
118 followers · starred Jun 2026
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.
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.
The goal of this repository is to act as a direct, automated translation layer:
.nix files.packages.x86_64-linux.<package>) via a flake, mapping names to translated packages wrapped with Guix-generated runtime environments.guix-metadata.json.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.
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
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=
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 restoreSETGIDbits, which the Nix sandbox strictly blocks. To successfully build from source, you need to disable syscall filtering.
[!IMPORTANT] Building from source also needs
/bin/shremoved from the Nix sandbox (--option sandbox-paths ''), otherwise some packages build differently than they do underguix-daemon. See Sandbox parity below. Prefer this over--option sandbox false, which exposes the host's/bin/shand 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 ''
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.
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 NixpkgsThe 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 namesTo 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.
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.
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.
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.
guix-transfer applies these fixes before any path is hashed, so the modified build and all downstream references stay consistent:
| Problem | Handled by |
|---|---|
| Bootstrap test suites fail under Nix's sandbox | guix-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 output | guix-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 specifier | guix-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.
49 followers · starred Aug 2026
72 followers · starred Sep 2026
259 followers · starred Jun 2026
118 followers · starred Jun 2026