Podman: Difference between revisions
imported>JohnAZoidberg No edit summary |
Add rootless Podman guide |
||
| (40 intermediate revisions by 31 users not shown) | |||
| Line 1: | Line 1: | ||
Podman can run rootless containers and be a drop-in replacement for Docker | [https://podman.io/ Podman] can run rootless containers and be a drop-in replacement for [[Docker]] | ||
== | == Setup == | ||
{{File|3=virtualisation.podman = { | |||
enable = true; | |||
dockerCompat = true; # Creates a symlink from docker to podman | |||
defaultNetwork.settings.dns_enabled = true; # Required for containers under podman-compose to be able to talk to each other. | |||
}; | |||
users.users.<USERNAME> = { # replace `<USERNAME>` with the actual username | |||
extraGroups = [ | |||
"podman" | |||
]; | |||
};|name=/etc/nixos/configuration.nix|lang=nix}} | |||
< | {{Security Warning|Beware that the podman group membership is effectively equivalent to being root, just like with Docker! <br> Consider using rootless podman.}} | ||
A reboot or re-login might be required for the permissions to take effect after applying changes | |||
== Tips and tricks == | |||
=== '''podman compose''' === | |||
podman compose is a thin wrapper around an external compose provider such as [https://github.com/docker/compose docker-compose] or [https://github.com/containers/podman-compose podman-compose]. This means that <code>podman compose</code> is executing another tool that implements the compose functionality but sets up the environment in a way to let the compose provider communicate transparently with the local Podman socket. The specified options as well as the command and argument are passed directly to the compose provider. | |||
The default compose providers are <code>docker-compose</code> and <code>podman-compose</code>. If installed, <code>docker-compose</code> takes precedence since it is the original implementation of the Compose specification. | |||
To change the default behavior or have a custom installation path for your provider of choice: <syntaxhighlight lang="nix">{ | |||
services.podman.settings.containers = { compose_providers = ["/path/to/provider"] }; | |||
}; | }</syntaxhighlight>You may also set the <code>PODMAN_COMPOSE_PROVIDER</code> environment variable:<syntaxhighlight lang="bash">PODMAN_COMPOSE_PROVIDER="/path/to/provider" podman compose up -d</syntaxhighlight>or:<syntaxhighlight lang="nix">{ | ||
environment.sessionVariables = { | |||
PODMAN_COMPOSE_PROVIDER = "/path/to/provider"; | |||
}; | |||
}</syntaxhighlight>By default, <code>podman compose</code> will emit a warning saying that it executes an external command. This warning can be disabled by setting <code>compose_warning_logs</code> to false in <code>services.podman.settings.containers</code> or setting the <code>PODMAN_COMPOSE_WARNING_LOGS</code> environment variable to false.<syntaxhighlight lang="nix">{ | |||
services.podman.settings.containers = { | |||
compose_providers = ["/path/to/provider"]; | |||
compose_warning_logs = false; | |||
}; | |||
}</syntaxhighlight><syntaxhighlight lang="nix"> | |||
{ | |||
environment.sessionVariables = { | |||
PODMAN_COMPOSE_PROVIDER = "/path/to/provider"; | |||
PODMAN_COMPOSE_WARNING_LOGS = false; | |||
}; | }; | ||
} | } | ||
</ | </syntaxhighlight> | ||
=== With ZFS === | |||
Rootless can't use [[ZFS]] directly but the overlay needs POSIX ACL enabled for the underlying ZFS filesystem, ie., <code>acltype=posixacl</code> | |||
Best to mount a dataset under <code>/var/lib/containers/storage</code> with property <code>acltype=posixacl</code>. | |||
=== Within nix-shell === | |||
From https://gist.github.com/adisbladis/187204cb772800489ee3dac4acdd9947 :<blockquote>{{File|3={ pkgs ? import <nixpkgs> {} }: | |||
let | |||
# To use this shell.nix on NixOS your user needs to be configured as such: | |||
# users.extraUsers.adisbladis = { | |||
# subUidRanges = [{ startUid = 100000; count = 65536; }]; | |||
# subGidRanges = [{ startGid = 100000; count = 65536; }]; | |||
# }; | |||
# Provides a script that copies required files to ~/ | |||
podmanSetupScript = let | |||
registriesConf = pkgs.writeText "registries.conf" '' | |||
[registries.search] | |||
registries = ['docker.io'] | |||
[registries.block] | |||
registries = [] | |||
''; | |||
in pkgs.writeScript "podman-setup" '' | |||
#!${pkgs.runtimeShell} | |||
# Dont overwrite customised configuration | |||
if ! test -f ~/.config/containers/policy.json; then | |||
install -Dm555 ${pkgs.skopeo.src}/default-policy.json ~/.config/containers/policy.json | |||
fi | |||
if ! test -f ~/.config/containers/registries.conf; then | |||
install -Dm555 ${registriesConf} ~/.config/containers/registries.conf | |||
fi | |||
''; | |||
# Provides a fake "docker" binary mapping to podman | |||
dockerCompat = pkgs.runCommandNoCC "docker-podman-compat" {} '' | |||
mkdir -p $out/bin | |||
ln -s ${pkgs.podman}/bin/podman $out/bin/docker | |||
''; | |||
in pkgs.mkShell { | |||
buildInputs = [ | |||
dockerCompat | |||
pkgs.podman # Docker compat | |||
pkgs.runc # Container runtime | |||
pkgs.conmon # Container runtime monitor | |||
pkgs.skopeo # Interact with container registry | |||
pkgs.slirp4netns # User-mode networking for unprivileged namespaces | |||
pkgs.fuse-overlayfs # CoW for images, much faster than default vfs | |||
]; | |||
shellHook = '' | |||
# Install required configuration | |||
${podmanSetupScript} | |||
''; | |||
== | }|name=podman-shell.nix|lang=nix}}</blockquote>Note that rootless podman requires newuidmap (from shadow). If you're not on NixOS, this cannot be supplied by the Nix package 'shadow' since [https://nixos.org/manual/nix/unstable/expressions/derivations.html setuid/setgid programs are not currently supported by Nix]. | ||
=== Containers as systemd services === | |||
<syntaxHighlight lang="nix"> | <syntaxHighlight lang="nix"> | ||
{ | { | ||
virtualisation.oci-containers.backend = "podman"; | |||
virtualisation.oci-containers.containers = { | |||
container-name = { | |||
image = "container-image"; | |||
autoStart = true; | |||
ports = [ "127.0.0.1:1234:1234" ]; | |||
}; | |||
}; | |||
} | } | ||
</syntaxHighlight> | </syntaxHighlight> | ||
=== | === Cross-architecture containers using binfmt/qemu === | ||
<syntaxHighlight lang="nix"> | |||
boot.binfmt = { | |||
emulatedSystems = [ "aarch64-linux" ]; | |||
preferStaticEmulators = true; # required to work with podman | |||
}; | |||
</syntaxHighlight> | |||
<syntaxhighlight lang="console"> | |||
$ podman run --arch arm64 'docker.io/alpine:latest' arch | |||
aarch64 | |||
</syntaxhighlight> | |||
=== DevContainers === | |||
Using Podman, it is possible that the process of creation of DevContainers' containers to become stuck at the "Please select an image URL" step. | |||
To avoid this issue, you might restrict its registries configuration. | |||
You can change the global registries with:<syntaxhighlight lang="nix"> | |||
virtualisation.containers.registries.search = [ "docker.io" ]; | |||
</syntaxhighlight> | |||
For user-scoped registries you can do using [[Home Manager]] manually: | |||
</ | {{File|3=# User-scoped `~/.config/containers/registries` | ||
xdg.configFile."containers/registries.conf".text = '' | |||
[registries.search] | |||
registries = ['docker.io'] | |||
'';|name=~/.config/home-manager/home.nix|lang=nix}} | |||
=== '''Rootless Podman containers''' === | |||
Rootless Podman containers can be run using Home Manager's <code>services.podman</code> module | |||
= | Create a user to run the rootless container: | ||
{{File|name=/etc/nixos/configuration.nix|3=users.groups.nginx-rootless = { | |||
name = "nginx-rootless"; | |||
}; | |||
users.users.nginx-rootless = { | |||
group = "nginx-rootless"; | |||
linger = true; # Ensures containers keep running even when user is not logged in | |||
isNormalUser = true; | |||
extraGroups = [ "podman" ]; | |||
shell = pkgs.bash; | |||
};|lang=nix}} | |||
Allow the new user to use Home Manager: | |||
{{File|name=/etc/nixos/configuration.nix|3=nix.settings.allowed-users = ["nginx-rootless"];|lang=nix}} | |||
Set up Home Manager configuration for the new user, e.g. in your <code>flakes.nix</code>: | |||
{{File|name=/etc/nixos/flakes.nix|3=nixosConfigurations.myhost = nixpkgs.lib.nixosSystem { | |||
system = "x86_64-linux"; | |||
modules = [ | |||
./configuration.nix | |||
home-manager.nixosModules.home-manager | |||
{ | { | ||
home-manager.useGlobalPkgs = true; | |||
home-manager.useUserPackages = true; | |||
home-manager.users.nginx-rootless = import ./home/podman/nginx-rootless.nix; | |||
} | } | ||
]; | |||
}|lang=nix}} | |||
Set up the container using Home Manager's <code>services.podman</code> module. Ensure <code>${config.home.homeDirectory}/www</code> exists beforehand | |||
{{File|name=/etc/nixos/home/podman/nginx-rootless.nix|3={ config, pkgs, ... }: | |||
{ | |||
home.username = "nginx-rootless"; | |||
home.homeDirectory = "/home/nginx-rootless"; | |||
home.stateVersion = "25.05"; | |||
services.podman = { | |||
enable = true; | |||
containers = { | |||
nginx = { | |||
image = "docker.io/library/nginx:latest"; | |||
ports = [ "8080:80" ]; | |||
volumes = [ | |||
"${config.home.homeDirectory}/www:/usr/share/nginx/html:ro" | |||
]; | |||
autoStart = true; | |||
}; | |||
}; | |||
}; | }; | ||
}|lang=nix}} | |||
==== Limitations and quirks ==== | |||
* Rootless containers can't bind to ports below 1024 by default. You can allow it system-wide with <code>boot.kernel.sysctl."net.ipv4.ip_unprivileged_port_start" = 80;</code>, or map a high port instead (<code>-p 8080:80</code>) | |||
* Rootless images and volumes are stored in <code>~/.local/share/containers/storage</code>, separate from root's storage | |||
* Files that a non-root container writes to a bind mount are owned by an unprivileged host UID (for example <code>100000–165535</code>), not by you. Use <code>:U</code> parameter on the volume mount to ensure the mount is owned by the user and group the container runs , or <code>--userns=keep-id</code> to change the UID inside the container to the one of the host user <ref>https://docs.podman.io/en/latest/markdown/podman-run.1.html#volume-v-source-volume-host-dir-container-dir-options</ref> | |||
[[Category:Software]] | |||
[[Category:Server]] | |||
[[Category:Container]] | |||
Latest revision as of 12:41, 27 September 2026
Podman can run rootless containers and be a drop-in replacement for Docker
Setup
virtualisation.podman = {
enable = true;
dockerCompat = true; # Creates a symlink from docker to podman
defaultNetwork.settings.dns_enabled = true; # Required for containers under podman-compose to be able to talk to each other.
};
users.users.<USERNAME> = { # replace `<USERNAME>` with the actual username
extraGroups = [
"podman"
];
};
Consider using rootless podman.
A reboot or re-login might be required for the permissions to take effect after applying changes
Tips and tricks
podman compose
podman compose is a thin wrapper around an external compose provider such as docker-compose or podman-compose. This means that podman compose is executing another tool that implements the compose functionality but sets up the environment in a way to let the compose provider communicate transparently with the local Podman socket. The specified options as well as the command and argument are passed directly to the compose provider.
The default compose providers are docker-compose and podman-compose. If installed, docker-compose takes precedence since it is the original implementation of the Compose specification.
To change the default behavior or have a custom installation path for your provider of choice:
{
services.podman.settings.containers = { compose_providers = ["/path/to/provider"] };
}
You may also set the PODMAN_COMPOSE_PROVIDER environment variable:
PODMAN_COMPOSE_PROVIDER="/path/to/provider" podman compose up -d
or:
{
environment.sessionVariables = {
PODMAN_COMPOSE_PROVIDER = "/path/to/provider";
};
}
By default, podman compose will emit a warning saying that it executes an external command. This warning can be disabled by setting compose_warning_logs to false in services.podman.settings.containers or setting the PODMAN_COMPOSE_WARNING_LOGS environment variable to false.
{
services.podman.settings.containers = {
compose_providers = ["/path/to/provider"];
compose_warning_logs = false;
};
}
{
environment.sessionVariables = {
PODMAN_COMPOSE_PROVIDER = "/path/to/provider";
PODMAN_COMPOSE_WARNING_LOGS = false;
};
}
With ZFS
Rootless can't use ZFS directly but the overlay needs POSIX ACL enabled for the underlying ZFS filesystem, ie., acltype=posixacl
Best to mount a dataset under /var/lib/containers/storage with property acltype=posixacl.
Within nix-shell
From https://gist.github.com/adisbladis/187204cb772800489ee3dac4acdd9947 :
❄︎ podman-shell.nix{ pkgs ? import <nixpkgs> {} }: let # To use this shell.nix on NixOS your user needs to be configured as such: # users.extraUsers.adisbladis = { # subUidRanges = [{ startUid = 100000; count = 65536; }]; # subGidRanges = [{ startGid = 100000; count = 65536; }]; # }; # Provides a script that copies required files to ~/ podmanSetupScript = let registriesConf = pkgs.writeText "registries.conf" '' [registries.search] registries = ['docker.io'] [registries.block] registries = [] ''; in pkgs.writeScript "podman-setup" '' #!${pkgs.runtimeShell} # Dont overwrite customised configuration if ! test -f ~/.config/containers/policy.json; then install -Dm555 ${pkgs.skopeo.src}/default-policy.json ~/.config/containers/policy.json fi if ! test -f ~/.config/containers/registries.conf; then install -Dm555 ${registriesConf} ~/.config/containers/registries.conf fi ''; # Provides a fake "docker" binary mapping to podman dockerCompat = pkgs.runCommandNoCC "docker-podman-compat" {} '' mkdir -p $out/bin ln -s ${pkgs.podman}/bin/podman $out/bin/docker ''; in pkgs.mkShell { buildInputs = [ dockerCompat pkgs.podman # Docker compat pkgs.runc # Container runtime pkgs.conmon # Container runtime monitor pkgs.skopeo # Interact with container registry pkgs.slirp4netns # User-mode networking for unprivileged namespaces pkgs.fuse-overlayfs # CoW for images, much faster than default vfs ]; shellHook = '' # Install required configuration ${podmanSetupScript} ''; }
Note that rootless podman requires newuidmap (from shadow). If you're not on NixOS, this cannot be supplied by the Nix package 'shadow' since setuid/setgid programs are not currently supported by Nix.
Containers as systemd services
{
virtualisation.oci-containers.backend = "podman";
virtualisation.oci-containers.containers = {
container-name = {
image = "container-image";
autoStart = true;
ports = [ "127.0.0.1:1234:1234" ];
};
};
}
Cross-architecture containers using binfmt/qemu
boot.binfmt = {
emulatedSystems = [ "aarch64-linux" ];
preferStaticEmulators = true; # required to work with podman
};
$ podman run --arch arm64 'docker.io/alpine:latest' arch
aarch64
DevContainers
Using Podman, it is possible that the process of creation of DevContainers' containers to become stuck at the "Please select an image URL" step.
To avoid this issue, you might restrict its registries configuration.
You can change the global registries with:
virtualisation.containers.registries.search = [ "docker.io" ];
For user-scoped registries you can do using Home Manager manually:
# User-scoped `~/.config/containers/registries`
xdg.configFile."containers/registries.conf".text = ''
[registries.search]
registries = ['docker.io']
'';
Rootless Podman containers
Rootless Podman containers can be run using Home Manager's services.podman module
Create a user to run the rootless container:
users.groups.nginx-rootless = {
name = "nginx-rootless";
};
users.users.nginx-rootless = {
group = "nginx-rootless";
linger = true; # Ensures containers keep running even when user is not logged in
isNormalUser = true;
extraGroups = [ "podman" ];
shell = pkgs.bash;
};
Allow the new user to use Home Manager:
nix.settings.allowed-users = ["nginx-rootless"];
Set up Home Manager configuration for the new user, e.g. in your flakes.nix:
nixosConfigurations.myhost = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
./configuration.nix
home-manager.nixosModules.home-manager
{
home-manager.useGlobalPkgs = true;
home-manager.useUserPackages = true;
home-manager.users.nginx-rootless = import ./home/podman/nginx-rootless.nix;
}
];
}
Set up the container using Home Manager's services.podman module. Ensure ${config.home.homeDirectory}/www exists beforehand
{ config, pkgs, ... }:
{
home.username = "nginx-rootless";
home.homeDirectory = "/home/nginx-rootless";
home.stateVersion = "25.05";
services.podman = {
enable = true;
containers = {
nginx = {
image = "docker.io/library/nginx:latest";
ports = [ "8080:80" ];
volumes = [
"${config.home.homeDirectory}/www:/usr/share/nginx/html:ro"
];
autoStart = true;
};
};
};
}
Limitations and quirks
- Rootless containers can't bind to ports below 1024 by default. You can allow it system-wide with
boot.kernel.sysctl."net.ipv4.ip_unprivileged_port_start" = 80;, or map a high port instead (-p 8080:80) - Rootless images and volumes are stored in
~/.local/share/containers/storage, separate from root's storage - Files that a non-root container writes to a bind mount are owned by an unprivileged host UID (for example
100000–165535), not by you. Use:Uparameter on the volume mount to ensure the mount is owned by the user and group the container runs , or--userns=keep-idto change the UID inside the container to the one of the host user [1]