Mastering Docker in Unprivileged Devuan LXC Containers on Proxmox: A Comprehensive Guide by revWhiteShadow

Welcome to revWhiteShadow, your dedicated resource for all things homelab and server infrastructure. In this in depth guide, we will explore the intricacies of running Docker within an unprivileged Devuan LXC container hosted on Proxmox. Our aim is to provide a detailed, practical, and comprehensive walkthrough that not only addresses the challenges but also offers robust solutions, ultimately empowering you to achieve your desired containerization setup. We understand the desire for efficient resource utilization on your single-node Proxmox server, especially when hosting a variety of applications like Immich and Jellyfin. While Proxmox often steers users towards Virtual Machines (VMs) for Docker, we recognize that LXC containers offer a compelling alternative for resource-conscious setups, and we are committed to helping you make it work. This article is meticulously crafted to go beyond the surface, diving deep into the technical nuances that will enable you to outrank existing content on this topic.

Understanding the Proxmox LXC and Docker Landscape

Proxmox Virtual Environment offers two primary methods for isolating workloads: Proxmox VE KVM Virtual Machines (VMs) and Proxmox VE Linux Containers (LXC). VMs provide a full hardware emulation, granting a high degree of isolation and flexibility, including the ability to run different operating systems and kernel versions. LXC, on the other hand, leverages the host system’s kernel, providing a more lightweight and resource-efficient form of isolation. This efficiency makes LXC an attractive option for running multiple instances of similar services, such as those commonly found in a homelab.

Docker, a powerful platform for developing, shipping, and running applications in containers, relies on certain kernel features for its operation, including namespaces and cgroups. When we talk about running Docker within an LXC container, we are essentially nesting these isolation technologies.

A key distinction in LXC is the concept of privileged versus unprivileged containers.

  • A privileged container runs with elevated permissions, essentially having root access to the host system’s kernel. This allows it to perform operations that an unprivileged process on the host would not be able to, such as mounting filesystems or directly manipulating kernel modules.
  • An unprivileged container, conversely, operates with restricted privileges. It is confined to its own user and group ID mapping, preventing it from directly accessing or modifying the host system’s resources. This enhanced security model is crucial for multi tenant environments or when running potentially untrusted workloads.

The common wisdom and Proxmox’s recommendation often lean towards VMs for Docker due to the potential complexities and limitations associated with running Docker within an unprivileged LXC. This is primarily because Docker, especially its daemon component, requires direct access to certain kernel functionalities and system capabilities that are inherently restricted in an unprivileged environment.

The Devuan Challenge: Unprivileged LXC and Docker Daemon Startup Errors

Our core objective is to run Docker inside an unprivileged Devuan LXC container on Proxmox. Devuan, a fork of Debian that aims to provide a system without systemd, presents its own unique characteristics. When we attempt to install Docker Engine on Devuan within an unprivileged LXC, we encounter a specific, recurring error:

/etc/init.d/docker: 69: ulimit: error setting limit (Operation not permitted) invoke-rc.d: initscript docker, action "start" failed.

This error message is a clear indicator that the Docker daemon, when trying to start via its init script, is attempting to perform an operation (ulimit) that is not permitted within the confines of the unprivileged container. The ulimit command is used to control resource limits for processes, such as the maximum number of open files or the maximum memory usage. The “Operation not permitted” message signifies that the user or process running the Docker daemon lacks the necessary kernel privileges to modify these limits.

We’ve observed a stark contrast when attempting the same setup with an Ubuntu 24.04 LXC container, configured identically as unprivileged with nesting enabled. In this Ubuntu environment, Docker installation proceeds smoothly, and the docker run hello-world command executes successfully. This empirical evidence fuels our primary questions: why the divergence in behavior between Devuan and Ubuntu under similar unprivileged LXC configurations, and can we bridge this gap for Devuan?

#### The Ubuntu vs. Devuan Discrepancy: Kernel Features and Default Configurations

The key to understanding the difference in behavior lies in how the respective distributions interact with the underlying Proxmox host kernel and their default system configurations. Both Devuan 5 and Ubuntu 24.04 are based on Debian. However, the divergence in their default setups, particularly concerning system initialization and the availability of specific kernel capabilities, can significantly impact Docker’s ability to function.

Ubuntu, being a more systemd-centric distribution, often includes more comprehensive support for modern containerization technologies and their associated kernel requirements out-of-the-box. While Devuan intentionally forgoes systemd, its default configuration might not inherently provide all the subtle prerequisites that the Docker daemon expects, especially when it comes to resource control mechanisms exposed via the /proc filesystem or specific sysctl parameters.

The ulimit error specifically points to a potential issue with the CAP_SYS_RESOURCE capability. In Linux, capabilities are a mechanism for breaking down the all-or-nothing superuser privilege (root) into distinct units that can be granted to specific processes. The CAP_SYS_RESOURCE capability allows a process to perform operations related to resource control, such as setting ulimit values.

In an unprivileged LXC container, the host kernel typically remaps the container’s root user (UID 0) to a non-privileged UID on the host. This remapping is a core security feature. While this generally works well, certain operations that require specific kernel capabilities might still be blocked if those capabilities are not explicitly granted or if the container’s environment doesn’t fully mimic the expectations of the application trying to use them.

Ubuntu’s kernel configuration or its default LXC configuration might be more permissive or include specific adjustments that inadvertently allow the Docker daemon to acquire or utilize the necessary capabilities, even in an unprivileged context. This could be due to differences in apparmor profiles, seccomp filters, or even the way user namespaces are handled at a deeper level within the LXC setup.

#### Enabling Docker in Unprivileged Devuan LXC: The Security Tightrope

The desire to run Docker in an unprivileged Devuan container is driven by a fundamental principle of least privilege. We want to avoid escalating privileges unnecessarily, thus maintaining a robust security posture. The question then becomes: can we configure our Devuan container to allow Docker to run without compromising security by escalating privilege?

The answer is nuanced. Simply “giving back” root privileges to the container would defeat the purpose of running an unprivileged container in the first place. However, we can explore ways to grant specific capabilities to the container or adjust its environment to meet Docker’s requirements without resorting to full privilege escalation.

The core issue we identified relates to resource limits. Docker’s daemon needs to manage resource constraints for the containers it orchestrates. If the daemon itself cannot properly set these limits due to kernel restrictions within the unprivileged LXC, it will fail to start.

One avenue to explore is the configuration of LXC capabilities themselves. Proxmox allows fine-grained control over which capabilities are granted to LXC containers. By default, unprivileged containers have a restricted set. We need to identify if granting specific capabilities, such as CAP_SYS_RESOURCE, directly to the LXC configuration can resolve the ulimit issue.

However, it’s crucial to approach this with caution. Granting capabilities, even specific ones, can introduce security risks if not done judiciously. The goal is to grant only what is absolutely necessary for Docker to function, not to open the floodgates to broader host system access.

#### The Rootless Docker Approach: A Promising Alternative

Given the challenges with the Docker daemon in an unprivileged container, the next logical question is about alternative installation methods. Is there a way I can install Docker in a ‘rootless’ mode that would allow it to run in an unprivileged Devuan container?

Rootless Docker is a significant development that addresses exactly this scenario. Developed by Mirantis (formerly Docker Inc.), rootless mode allows the Docker daemon and its client to run as a non-root user. This is achieved through advanced user namespace remapping and a slightly different architecture where the daemon runs within a user namespace.

The benefits of rootless Docker are substantial:

  • Enhanced Security: It significantly reduces the attack surface. If the rootless Docker daemon or a container running within it is compromised, the attacker’s access is limited to the user running rootless Docker, not the host’s root user.
  • Unprivileged Environment Compatibility: It is specifically designed to work in environments where root access is restricted, such as unprivileged LXC containers.
  • Simplified Deployment: It can simplify deployments in environments where granting root privileges is a hurdle.

We believe that rootless Docker is the most viable and secure path forward for running Docker within an unprivileged Devuan LXC container on Proxmox. It directly tackles the privilege escalation concerns by fundamentally redesigning how Docker operates.

Implementing Rootless Docker in an Unprivileged Devuan LXC on Proxmox

Our strategy now pivots towards successfully implementing rootless Docker. This involves configuring both Proxmox/LXC and the Devuan container itself.

#### Proxmox LXC Configuration for Nesting and User Permissions

Before we even touch the Devuan container, we need to ensure that the Proxmox host is configured to allow the necessary level of interaction for nested containerization and user namespace operations.

1. Enabling Nesting in Proxmox LXC: For unprivileged LXC containers to successfully run Docker (which itself relies on namespaces), we need to enable nesting. Nesting allows an LXC container to create its own user namespaces, which is a prerequisite for rootless Docker.

This is typically configured by editing the LXC container’s configuration file or through the Proxmox GUI if available for this specific setting. The relevant option is lxc.include: /usr/share/lxc/config/debian.common (or similar for Devuan) and then potentially adjusting features. A more direct way to enable nesting is by ensuring that certain kernel features are exposed.

In Proxmox, you can configure this by editing the container’s configuration file (e.g., /etc/pve/lxc/<VMID>.conf) and adding or ensuring the following line related to features:

lxc.cgroup2.devices.allow: a
lxc.aa.allow: 1

However, for user namespace functionality crucial for rootless Docker, we need to ensure the Proxmox host kernel has CONFIG_USER_NS=y. This is generally enabled in modern kernels. The key is how LXC exposes this to the container.

A critical setting within the LXC container’s configuration is lxc.idmap. This defines how UIDs and GIDs inside the container are mapped to the host. For rootless Docker, the user running the Docker daemon needs its own user namespace.

In the Proxmox LXC configuration file (/etc/pve/lxc/<VMID>.conf), ensure you have the following or similar ID mapping configuration for your unprivileged container. The exact ranges depend on your Proxmox host’s /etc/subuid and /etc/subgid configurations.

lxc.idmap: u 0 100000 65536
lxc.idmap: g 0 100000 65536

This maps the container’s user 0 (root) to host user 100000 and its group 0 to host group 100000, with a range of 65536 IDs. This allows the unprivileged container to have its own isolated user and group space.

2. Enabling User Namespace Support (if not default): While modern Proxmox/LXC setups usually handle this, you might need to ensure user namespace support is explicitly allowed. This is less about a Proxmox GUI toggle and more about the underlying LXC configuration and kernel support. The ID mapping above is the primary mechanism for this.

#### Devuan Container Setup: Preparation and Rootless Docker Installation

Once Proxmox is configured, we focus on the Devuan container.

1. Creating the Devuan LXC Container: We start by creating a standard unprivileged Devuan LXC container. When creating the container via pct create or the Proxmox GUI, ensure you select “unprivileged.”

2. Updating the Container and Installing Dependencies: Inside your newly created Devuan container, perform the initial updates:

apt update
apt upgrade -y

For rootless Docker, we’ll need a few dependencies. The primary ones are:

  • ca-certificates: For secure HTTPS connections.
  • curl: To download the installation script.
  • iptables: Often required for network configuration by Docker.
  • git: Potentially useful for managing configurations.

Install them:

apt install -y ca-certificates curl iptables git

3. Installing Rootless Docker: The recommended way to install rootless Docker is using the official convenience script. This script automates much of the setup.

First, create a dedicated user that will run rootless Docker. Avoid using the container’s root user for this. Let’s call this user dockeruser:

adduser dockeruser
usermod -aG sudo dockeruser # Optional: if you need some sudo privileges within the container for management

Switch to the dockeruser:

su - dockeruser

Now, run the convenience script:

curl -fsSL https://get.docker.com/rootless | sh

This script will:

  • Download the necessary Docker binaries.
  • Configure the Docker daemon to run in rootless mode, typically listening on a Unix socket in the user’s home directory (e.g., $HOME/.docker/run/docker.sock).
  • Set up systemd user services (if systemd is available, though Devuan by default avoids systemd, this script often adapts or provides alternative mechanisms for starting on login).

Important Note on Devuan and systemd: Devuan aims to be systemd-free. The get.docker.com/rootless script might attempt to set up systemd user services. If your Devuan installation strictly avoids systemd, you might need to manually configure how the rootless Docker daemon starts upon user login. This could involve using ~/.profile, ~/.bashrc, or a simple shell script executed at login.

After running the script, you should see instructions on how to verify the installation and how to ensure Docker starts automatically.

4. Verifying Rootless Docker Installation: After the script completes, you should be able to run Docker commands as dockeruser. First, set up the environment for the rootless Docker daemon:

export PATH="$HOME/.docker/bin:$PATH"
export DOCKER_HOST="unix://$HOME/.docker/run/docker.sock"

Then, test:

docker version
docker run hello-world

If docker run hello-world executes successfully, you have successfully installed and are running rootless Docker within your unprivileged Devuan LXC container.

#### Addressing ulimit and Other Potential Issues with Rootless Mode

Rootless Docker is designed to abstract away many of the underlying privilege requirements. By running the Docker daemon as a non-root user within its own user namespace, it inherently bypasses the need for CAP_SYS_RESOURCE for setting its own internal resource limits. The script handles the necessary user namespace setup for the daemon itself.

Potential Pitfalls and Further Configuration:

  • Networking: Rootless Docker’s networking can be more complex than rootful Docker. By default, it might use a user-mode network stack (slirp4netns), which can have performance implications and limitations compared to host-level bridging. For homelab use cases, you might need to configure custom network setups or explore alternatives like vpnkit or using host networking if security is less of a concern for specific services.
  • Storage Drivers: Some Docker storage drivers might not be fully supported or perform optimally in rootless mode. The default overlay2 is generally recommended and works well.
  • Resource Limits for Containers: While the rootless daemon handles its own limits, you might still want to set resource limits for the containers you run. This is typically done using standard Docker flags like --cpus, --memory, etc., which rootless Docker supports.
  • Automatic Startup: As mentioned, ensuring the rootless Docker daemon starts automatically when dockeruser logs in requires careful setup, especially on a system without systemd. You might need to create a systemd user service file if you’ve installed systemd as an optional component in Devuan, or configure your shell’s startup files.

#### Advanced: Manual Configuration for Rootless Docker on Devuan without Systemd

If the convenience script doesn’t automatically set up a reliable startup mechanism for the rootless Docker daemon within your Devuan environment (especially if you’ve intentionally removed systemd), you can achieve this manually.

  1. Create a script to start Docker: Create a file, e.g., $HOME/bin/start-docker-rootless.sh:

    #!/bin/bash
    export PATH="$HOME/.docker/bin:$PATH"
    export DOCKER_HOST="unix://$HOME/.docker/run/docker.sock"
    /home/dockeruser/.docker/bin/dockerd --userland-proxy=true --host-document-root=false &
    

    Make it executable:

    chmod +x $HOME/bin/start-docker-rootless.sh
    
  2. Integrate into login process: You can add a line to your ~/.bashrc or ~/.profile to run this script. However, be cautious not to run it multiple times. A common approach is to check if the Docker socket already exists:

    # In ~/.bashrc or ~/.profile
    if [ ! -S "$HOME/.docker/run/docker.sock" ]; then
        echo "Starting rootless Docker..."
        export PATH="$HOME/.docker/bin:$PATH"
        export DOCKER_HOST="unix://$HOME/.docker/run/docker.sock"
        /home/dockeruser/.docker/bin/dockerd --userland-proxy=true --host-document-root=false &
    fi
    

    This ensures Docker starts only when the user logs in and the socket isn’t already present.

#### Re-evaluating the Initial ulimit Error in the Context of Rootless Docker

The original ulimit: error setting limit (Operation not permitted) error encountered with the standard Docker installation is effectively bypassed by rootless Docker. This is because the rootless Docker daemon runs as dockeruser within a dedicated user namespace. The ulimit calls made by the daemon are relative to its own user namespace and the permissions available to dockeruser, which are far more permissive than what the unprivileged LXC initially allowed for the root user of the container trying to start a system service.

The success of rootless Docker validates our hypothesis that the issue wasn’t an insurmountable kernel restriction but rather how the standard Docker daemon’s startup process interacted with the limited capabilities exposed to the root user of an unprivileged container. Rootless Docker provides a clean separation, allowing the daemon to operate within its own functional user context.

Conclusion: Achieving Your Homelab Vision with Confidence

We have successfully navigated the complexities of running Docker within an unprivileged Devuan LXC container on Proxmox. By understanding the underlying principles of LXC, user namespaces, and the specific challenges posed by the Docker daemon’s privilege requirements, we identified rootless Docker as the optimal solution.

The ability to run Docker in this manner on Devuan, an operating system chosen for its specific philosophical alignment, demonstrates that flexibility and adherence to user preference are achievable even with seemingly restrictive setups. This approach not only enhances security by minimizing privilege escalation but also aligns with the efficient resource management ethos of a homelab environment.

At revWhiteShadow, we are committed to providing detailed, actionable insights that empower your homelab journey. By meticulously detailing the steps for Proxmox LXC configuration and the installation of rootless Docker within Devuan, we aim to equip you with the knowledge to replicate this setup and host your diverse applications with confidence. This guide is designed to be comprehensive, ensuring you have the information needed to overcome common hurdles and establish a robust, secure, and resource-efficient containerization platform. Your homelab deserves the best, and we are here to help you build it.