{"id":50975341,"url":"https://github.com/RaphaelRibes/pixitainer","last_synced_at":"2026-07-07T05:00:28.541Z","repository":{"id":327560368,"uuid":"1109780115","full_name":"RaphaelRibes/pixitainer","owner":"RaphaelRibes","description":"Containerize your Pixi workspace with a single command!","archived":false,"fork":false,"pushed_at":"2026-07-01T07:43:29.000Z","size":9038,"stargazers_count":37,"open_issues_count":0,"forks_count":2,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-07-01T09:23:24.031Z","etag":null,"topics":["apptainer","docker","pixi","reproducibility","reproducible-research","reproducible-science","singularity"],"latest_commit_sha":null,"homepage":"","language":"Shell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/RaphaelRibes.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-12-04T09:19:00.000Z","updated_at":"2026-06-18T10:00:21.000Z","dependencies_parsed_at":"2026-03-19T16:02:05.784Z","dependency_job_id":null,"html_url":"https://github.com/RaphaelRibes/pixitainer","commit_stats":null,"previous_names":["raphaelribes/pixitainer"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/RaphaelRibes/pixitainer","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RaphaelRibes%2Fpixitainer","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RaphaelRibes%2Fpixitainer/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RaphaelRibes%2Fpixitainer/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RaphaelRibes%2Fpixitainer/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/RaphaelRibes","download_url":"https://codeload.github.com/RaphaelRibes/pixitainer/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RaphaelRibes%2Fpixitainer/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35215221,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-07T02:00:07.222Z","response_time":90,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["apptainer","docker","pixi","reproducibility","reproducible-research","reproducible-science","singularity"],"created_at":"2026-06-19T07:02:10.881Z","updated_at":"2026-07-07T05:00:28.529Z","avatar_url":"https://github.com/RaphaelRibes.png","language":"Shell","funding_links":[],"categories":["Extensions"],"sub_categories":["Example projects"],"readme":"# Pixitainer\n\n![Version](https://img.shields.io/badge/Version-0.8.2-blue)\n[![Pixi Badge](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/prefix-dev/pixi/main/assets/badge/v0.json)](https://pixi.sh)\n[![License](https://img.shields.io/badge/Licence-BSD--3--Clause-green)](LICENSE)\n[![Forge](https://img.shields.io/badge/Forge-Prefix.dev--Forge-yellow)](https://prefix.dev/channels/raphaelribes/packages/pixitainer)\n[![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.19698995.svg)](https://doi.org/10.5281/zenodo.19698995)\n\n\n![banner](images/Banner_Gemini_Generated_Image.png)\n*This banner placeholder was generated by [Gemini 3 Banana Pro](https://gemini.google.com) using already made assets like the pixi, apptainer and singularity logo*\n\n## Why...\n\n### ...pixi ?\n\nPixi is a fast, easy and fun to use tool that allows you to manage dependencies across multiple environments very easily.\nIt has great support from its developers and community on [discord](https://discord.gg/A94bgPENFD).\n\n### ...using containers ?\n\nSingularity/Apptainer is widely used in bioinformatics, it's at the heart of analysis pipelines, but it's a headache to set up.\nOn the other hand, pixi is fast and easy to use, but it's not a container and is harder to keep intact over long periods.\nThe idea behind pixitainer is simple: put a pixi environment into an Apptainer container, so you can freeze your fast-paced working environment into a portable image!\n\n## Apptainer vs Singularity\n\nApptainer is more the \"public library\" version of the software, while Singularity is more like a \"corporate bookstore.\"\nBecause Apptainer is hosted by the Linux Foundation, it is designed specifically for the scientific community to ensure that your research code remains free, accessible and without being tied to a private company’s profit goals.\n\nSingularity is often found on high-performance computing (HPC) clusters and is maintained by Sylabs. Pixitainer supports both!\n\n## Projects using pixitainer\n\n- [FastDedup](https://github.com/RaphaelRibes/FDedup.git) a PCR deduplication tool written in Rust optimized for speed and low memory usage.\n\n## How to install\n\n1. Install pixi\n\n```bash\ncurl -fsSL https://pixi.sh/install.sh | sh\n```\n\nInstall pixitainer (Apptainer), pixitainer-singularity (Singularity) or pixitainer-docker (Docker) globaly on pixi:\n\n```bash\n# For Apptainer\npixi global install -c https://prefix.dev/raphaelribes -c https://prefix.dev/conda-forge pixitainer\n\n# For Singularity\npixi global install -c https://prefix.dev/raphaelribes -c https://prefix.dev/conda-forge pixitainer-singularity\n\n# For Docker\npixi global install -c https://prefix.dev/raphaelribes -c https://prefix.dev/conda-forge pixitainer-docker\n```\n\n### Developer\n\n1. Clone this repo\n\n```bash\ngit clone https://github.com/RaphaelRibes/pixitainer.git\ncd pixitainer\n```\n\n2. Build the pixitainer extension\n\n```bash\npixi build\n```\n\n3. Install the pixitainer extension\n\n```bash\n# Apptainer\npixi global install pixitainer --path pixitainer-0.8.2*.conda --channel conda-forge\n\n# Singularity\npixi global install pixitainer-singularity --path pixitainer-singularity-0.8.2*.conda --channel conda-forge\n\n# Docker\npixi global install pixitainer-docker --path pixitainer-docker-0.8.2*.conda --channel conda-forge\n```\n\n## How to use\n\nThere are two ways of using pixitainer:\n\n1. Seamlessly (default)\n2. Manually (`-m`, `--manual`)\n\nWe put ourselves in an environment with one task defined like\n\n```yaml\n[tasks]\nmake_dir = 'mkdir testdir'\n```\n\n### Seamlessly (default)\n\nSeamless execution is the default, so no flag is needed.\n\n```bash\n# For Apptainer\npixi containerize\n\n# For Singularity\npixi containerize-singularity\n\n# For Docker\npixi containerize-docker\n```\n\nYou can then run your task like pixi is not even here\n\n```bash\n# Apptainer\napptainer run -f pixitainer.sif make_dir\n\n# Singularity\nsingularity run -f pixitainer.sif make_dir\n\n# Docker\ndocker run --rm \u003cyour_image_name\u003e:latest make_dir\n```\n\n\u003e **WARNING**: in seamless mode, every command run through the image is executed like so\n\u003e\n\u003e ```bash\n\u003e pixi run --locked --as-is -m /opt/conf/pixi.toml \"$@\"\n\u003e ```\n\u003e\n\u003e Meaning that you only have access to `pixi run`. Use `--manual` if you need a raw shell entrypoint.\n\n### Manually (`-m`, `--manual`)\n\nPass `-m` / `--manual` to get a plain shell entrypoint instead, so you invoke `pixi` yourself.\n\n```bash\n# For Apptainer\npixi containerize --manual\n\n# For Singularity\npixi containerize-singularity --manual\n\n# For Docker\npixi containerize-docker --manual\n```\n\nThen you call pixi inside your image\n\n```bash\n# Apptainer\napptainer run -f pixitainer.sif pixi run --as-is -m /opt/conf/pixi.toml make_dir\n\n# Singularity\nsingularity run -f pixitainer.sif pixi run --as-is -m /opt/conf/pixi.toml make_dir\n\n# Docker\ndocker run --rm \u003cyour_image_name\u003e:latest pixi run --as-is -m /opt/conf/pixi.toml make_dir\n```\n\nWe add `--as-is` to make sure it sticks to the `pixi.lock` file, and it only uses the installed binaries and doesn't try to install others.\n\n\u003e **WARNING**: `-m /opt/conf/pixi.toml` is mandatory or pixi will use the default one in your current working directory.\n\n## Containerizing a single tool (`tool`)\n\nSince **v0.8.0**, you can containerize one or more conda packages directly with `pixi global install`, **without needing a `pixi.toml` or `pixi.lock`**. The `tool` subcommand can be run from anywhere on your system.\n\n```bash\n# Apptainer\npixi containerize tool python                 # -\u003e python.sif\npixi containerize tool -c bioconda fastp      # -\u003e fastp.sif\n\n# Singularity\npixi containerize-singularity tool -c bioconda samtools\n\n# Docker\npixi containerize-docker tool -c bioconda fastp   # -\u003e fastp:latest\n```\n\nPackages use the conda MatchSpec syntax, so you can pin versions inline, like for pixi packages:\n\n```bash\npixi containerize tool 'python\u003e=3.11'\n```\n\nYou can install several packages into one image (each gets its own global environment, all binaries exposed):\n\n```bash\npixi containerize tool -c bioconda samtools bcftools\n```\n\nThe image name defaults to the (first) package name (`\u003cpackage\u003e.sif` for SIF, `\u003cpackage\u003e:latest` for Docker). Override it with `-o/--output`:\n\n```bash\npixi containerize tool -c bioconda fastp -o images/fastp-0.23.sif\npixi containerize-docker tool -c bioconda fastp -o myregistry/fastp:0.23\n```\n\nThen run the tool from the image. By default the tool's binary is the entrypoint, so you run it directly:\n\n```bash\napptainer run fastp.sif fastp --version\ndocker run --rm fastp:latest fastp --version\n```\n\n\u003e **Note**: with several packages (e.g. `tool samtools bcftools`) there is no single binary to bind, so the image falls back to the manual entrypoint automatically; run each tool by name.\n\n### `tool` options\n\n| Option                                | Meaning                                                                                                                                                   |\n|---------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------|\n| `-c, --channel CHAN`                  | Channel to pull from (repeatable, default `conda-forge`). When you set a channel, `conda-forge` is auto-appended if missing, since most packages need it. |\n| `-o, --output`                        | Output `.sif` path / Docker tag. Defaults to the package name.                                                                                            |\n| `-m, --manual`                        | Use a shell entrypoint instead of the tool binary. By default the package's binary is the image entrypoint, so you run the tool directly.                 |\n| `-b, --base-image`                    | Base image (default: `debian:stable-slim`, a small glibc base).                                                                                           |\n| `-a, --add-file SRC:DEST`             | Add an extra file/folder to the image (repeatable).                                                                                                       |\n| `--post-command CMD`                  | Run an extra command after install (repeatable).                                                                                                          |\n| `-l, --label KEY:VALUE`               | Add a custom label (repeatable).                                                                                                                          |\n| `-k, --keep-def`                      | Keep the generated `.def` / `Dockerfile`.                                                                                                                 |\n| `-d, --dry-run`                       | Print the `.def` / `Dockerfile` without building.                                                                                                         |\n| `-q, --quiet` / `-v, --verbose`       | Output verbosity.                                                                                                                                         |\n\nPin a version with the MatchSpec syntax (`fastp=0.23.4`, `'python\u003e=3.11'`) rather than a dedicated flag.\n\n### Keeping `tool` images small\n\n`tool` images are optimised for size automatically:\n\n- The default base is `debian:stable-slim` rather than a host-matched image. Since every package comes from conda, the base OS is irrelevant, so a small glibc base is used (override with `-b`).\n- The pixi download cache (`~/.cache/rattler`) is removed after install. In containers this cache is copied rather than hard-linked, so dropping it is a large saving.\n- The pixi binary itself is removed: it is only needed at build time. The exposed tool binaries are standalone trampolines under `/opt/pixi/bin`, so they keep working without it.\n- For Docker, install and cleanup happen in a single layer, so the cache, the pixi binary and the build-time `curl` never persist in a lower layer.\n\nIf you need to trim further (for example stripping `man`/`include`/`*.a` from the env), add a `--post-command`, keeping in mind that compiler-like tools may need those files.\n\n\u003e **WARNING**: inside `tool` mode, `-c` means `--channel` (not `--post-command`). Use the long form `--post-command` for post commands. This differs from the manifest-based commands, where `-c` is `--post-command`.\n\n\u003e **Note**: `tool` mode does not read the `[tool.pixitainer]` TOML tables, since it is meant to run standalone without a project manifest.\n\n## TOML Configuration\n\nSince **v0.6.0**, you can configure pixitainer options directly in your project manifest (`pixi.toml` or `pyproject.toml`). Since **v0.7.0**, the configuration supports a three-tier priority system.\n\n### Priority order (lowest → highest)\n\n```\n[tool.pixitainer]  \u003c  [tool.pixitainer.\u003cbackend\u003e]  \u003c  CLI arguments\n```\n\n### `[tool.pixitainer]` — shared defaults\n\nKeys placed here apply to **all backends** (Apptainer, Singularity, Docker) unless overridden.\n\n```toml\n[tool.pixitainer]\noutput = \"my_image.sif\"\nbase-image = \"ubuntu:24.04\"\nmanual = false   # default; set true for a raw shell entrypoint\nenv = [\"default\"]\nadd-file = [\"data/config.yaml:/opt/config.yaml\"]\npost-command = [\"echo 'Setup done' \u003e /opt/setup.log\"]\nlabel = [\"APP_VERSION:1.0.0\", \"AUTHOR:me\"]\nkeep-def = false\ndry-run = false\nquiet = false\nverbose = false\npixi-version = \"0.64.0\"\n```\n\n### `[tool.pixitainer.apptainer]` / `[tool.pixitainer.singularity]` / `[tool.pixitainer.docker]` — backend overrides\n\nKeys placed in a backend-specific table **override** the matching key from `[tool.pixitainer]` for that backend only. Scalar keys fully replace the general value. **Array keys (`env`, `add-file`, `post-command`, `label`, and the Docker-specific arrays) fully replace** the general array — they are not merged or appended.\n\n```toml\n[tool.pixitainer]\noutput = \"my_image.sif\"\nbase-image = \"ubuntu:24.04\"\nmanual = false\nlabel = [\"AUTHOR:me\"]\n\n# When running `pixi containerize` (Apptainer):\n# output → \"my_apptainer.sif\", label → [\"AUTHOR:me\", \"APPTAINER:yes\"]\n[tool.pixitainer.apptainer]\noutput = \"my_apptainer.sif\"\nlabel = [\"AUTHOR:me\", \"APPTAINER:yes\"]\n\n# When running `pixi containerize-docker`:\n# output → \"my_image\" (tag), user → \"nobody\", label stays [\"AUTHOR:me\"] from general\n[tool.pixitainer.docker]\noutput = \"my_image\"\nuser = \"nobody\"\n```\n\nDocker-specific keys that are only valid under `[tool.pixitainer.docker]`:\n\n```toml\n[tool.pixitainer.docker]\noutput = \"my_image:latest\"\nuser = \"nobody\"\nworkdir = \"/workspace\"\nno-cache = false\npush = false\nsquash = false\nplatform = \"linux/amd64\"\nnetwork = \"host\"\nsave = \"my_image.tar.gz\"\nbuild-arg = [\"KEY=value\"]\nsecret = [\"id=token,src=/path/to/token\"]\nssh = [\"default\"]\ntag = [\"registry.example.com/my_image:latest\"]\ncache-from = [\"type=registry,ref=ghcr.io/me/img:cache\"]\ncache-to = [\"type=inline\"]\n```\n\n\u003e **Note**: All keys mirror the long-form CLI option names (without the `--` prefix). Boolean options use `true`/`false`, and array options use TOML array syntax.\n\n### CLI overrides everything\n\nCommand-line arguments always win over both TOML tables. This lets you define sensible defaults in your manifest and override them on a per-build basis:\n\n```bash\n# Uses TOML defaults, but overrides the output path\npixi containerize -o custom_output.sif\n```\n\n## Known problems\n\n### Pathing is... strange?\n\nWhen launching a command in the pixi shell, the task's working directory changes to the pixi project root (`PIXI_PROJECT_ROOT`).\n\nLet's create a task\n\n```yaml\n[tasks]\nmake_dir = 'mkdir testdir'\n```\n\nIf you run this task, it creates `$PIXI_PROJECT_ROOT/testdir` (`/opt/conf/testdir`) rather than `$INIT_CWD/testdir` (`$(pwd)/testdir`).\nTo work around this, prefix your paths with `$INIT_CWD/` instead:\n\n```bash\nmake_dir = 'mkdir $INIT_CWD/testdir'\n```\n\n\u003e **Note**: The container now defaults to `/opt/conf` as the working directory (`pwd -P` returns `/opt/conf`).\n\u003e This ensures compatibility with internal path resolutions, but you should rely on `$INIT_CWD` for input/output relative to where you run the container.\n\u003e If you have files to import in the container (ex: your build) you can put it in `/opt/conf` and it will allow normal execution.\n\u003e However, be conscious that your outputs will also be in `/opt/conf` and not in your current working directory.\n\u003e Since you cannot write in a container, you will have to specify the outputs with `$INIT_CWD` and bind the folder where you want your outputs to be.\n\n### Read-only file system (os error 30)\n\nThis is related to the previous problem: pixi is using `PIXI_PROJECT_ROOT` as the `cwd`.\nIt will try to write in `/opt/conf` which is not allowed because the SIF image is in read-only mode.\nTo fix it, replace `mkdir test` by `mkdir $INIT_CWD/test`.\n\nHowever, sometimes pixi may write something in its cache so don't hesitate to use `--writable-tmpfs`.\n\n### Failed to create mount namespace\n\nOn Debian-based distros like Ubuntu, you may get this error message. My solution was to add an alias to `.bashrc`:\n```sh\nalias fixnamespace=\"sudo sysctl -w kernel.unprivileged_userns_clone=1 \u0026\u0026 sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0\"\n```\nRunning `fixnamespace` will fix your namespace! However you will have to type it again every time you start your computer.\n\n## How ?\n\nThis was implemented as a [pixi extension](https://pixi.sh/latest/integration/extensions/introduction/), so you just type `pixi containerize`, add some options, and tada!\n\u003e Note that pixitainer will install pixi with the same version that you have on your machine — you can change this with the options below.\n\nTODO:\n\n- [x] Recipe that works.\n- [x] Pixi package that I can add as an extension.\n- [x] Adding options to the extension.\n  - [x] Core Options\n    - [x] Output image path (`-o`, `--output`)\n    - [x] Working directory (`-p`, `--path`)\n    - [x] Manual (shell) entrypoint opt-out (`-m`, `--manual`); seamless is the default\n  - [x] Environment \u0026 Image Setup\n    - [x] Specify base image (`-b`, `--base-image`)\n    - [x] Specific environment selection (`-e`, `--env`)\n    - [x] No installation of environment in the container (`-n`, `--no-install`)\n  - [x] Pixi Versioning\n    - [x] Specify pixi version (`-V`, `--pixi-version`)\n    - [x] Latest pixi version (`-L`, `--latest`)\n  - [x] Advanced Modifications\n    - [x] Add extra files/folders (`-a`, `--add-file`)\n    - [x] Run extra post commands (`-c`, `--post-command`)\n    - [x] Add extra labels (`-l`, `--label`)\n    - [x] Export the `.def` file (`-k`, `--keep-def`)\n  - [x] Output Options\n    - [x] Dry-run (`-d`, `--dry-run`)\n  - [x] General Options\n    - [x] Quiet mode (`-q`, `--quiet`)\n    - [x] Verbose mode (`-v`, `--verbose`)\n  - [x] Docker-specific Options\n    - [x] Build without cache (`--no-cache`)\n    - [x] Push image to registry (`--push`)\n    - [x] Multi-platform builds (`--platform`)\n    - [x] Squash all layers (`--squash`)\n    - [x] Build-time network mode (`--network`)\n    - [x] Build-time ARG variables (`--build-arg`)\n    - [x] BuildKit secret mounts (`--secret`)\n    - [x] SSH agent forwarding (`--ssh`)\n    - [x] Extra image tags (`-t`, `--tag`)\n    - [x] Cache reuse from registry (`--cache-from`)\n    - [x] Cache export after build (`--cache-to`)\n    - [x] Export image to archive (`--save`)\n    - [x] Run container as user (`--user`)\n    - [x] Override container working directory (`--workdir`)\n  - [x] Support to containerize a specific tool, without need a pixi manifest\n    - [x] e.g `pixi containerize tool -c/--channel \u003cchannel\u003e \u003ctool_name\u003e` (version pinned via MatchSpec, e.g. `tool fastp=0.23.4`)\n- [x] Support the options in a `[tool.pixitainer]` table in the manifest\n  - [x] Support backend-specific subtables (`[tool.pixitainer.\u003cbackend\u003e]`)\n- [x] Support of container solutions\n  - [x] Apptainer\n  - [x] Singularity\n  - [x] Docker\n- [x] Tests.\n- [x] Publish\n- [ ] Go back to step 3 until WW3, messiah or death of the internet\n\n## License\n\nPixitainer is licensed under the BSD 3-Clause License. See the [LICENSE](LICENSE) file for more details.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FRaphaelRibes%2Fpixitainer","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FRaphaelRibes%2Fpixitainer","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FRaphaelRibes%2Fpixitainer/lists"}