十一月 10, 2025

在proxmox ve中用nixos lxc构建尽量无状态的容器

 容器的初次构建和后续用backup文件重新部署差别虽然不大,但是有些一次性操作的关键环节如果漏掉了,重新找起来也挺麻烦,还是都记下。


初始化过程主要参照这个说明

https://nixos.wiki/wiki/Proxmox_Linux_Container


1、nixos的pve lxc镜像:

访问 https://hydra.nixos.org/jobset/nixos/trunk-combined#tabs-jobs

在搜索栏中输入proxmox,选择最近构建的proxmoxlxc任务结果tar文件下载

完了到pve里有ct templates的存储位置里上传

对网络连接有自信的当然也可以在这里download from url


存在local的话可以确认下  /var/lib/vz/template/cache/ 路径下有对应文件


2、创建容器 ,可以抄说明里的命令。其实我用网页创建也没碰到什么问题,无特权容器去掉,回头到options勾上nesting就行。


ctid="99999"

ctname="nixos"

ctt="local:vztmpl/nixos-system-x86_64-linux.tar.xz"

cts="local-lvm"     


pct create ${ctid} ${ctt} \

  --hostname=${ctname} \

  --ostype=nixos --unprivileged=0 --features nesting=1 \

  --net0 name=eth0,bridge=vmbr0,ip=dhcp \

  --arch=amd64 --swap=1024 --memory=2048 \

  --storage=${cts}



nixos大量小文件的模式一般会在磁盘容量塞满之前先把inode塞爆,懒得事先给虚拟磁盘做更小分块的话,就把磁盘调大点,按照jellyfin的经验,8g应该是不够的。


以上命令默认磁盘4g,可以加到16.

pct resize ${ctid} rootfs +12G


3、(可选)如果已经提前准备好了nix配置文件和应用的config以及data文件夹,可以在pve里先妥善组织好,用mountpoint挂载到容器里。没有的话就跳过,回头来弄也没差。

我的目录大致如下

nix/

├── nixfin/

│   ├── nixos-config

│   │   ├── configuration.nix

│   │   ├── smb.nix

│   │   └── jellyfin.nix

│   ├── jellyfin-data

│   └── jellyfin-config

├── nixpi

├── nixlxc

├── ...

└── others

在/etc/pve/lxc/999.conf里添加一行,根据实际情况调整绝对路径

 注意格式

mp0: /nix-test/nixlxc,mp=/etc/nixos


4、启动容器,

进入容器没有环境,得先执行这个才有各种命令

source /etc/set-environment


5、nixchannel,重建

这个就是尽量无状态的尽量了,nixchannel没啥其他地方定义,即便用了flake,你总是得进容器手工跑重建,

nix-channel --add https://mirrors.ustc.edu.cn/nix-channels/nixpkgs-unstable nixpkgs

nix-channel --add https://mirrors.tuna.tsinghua.edu.cn/nix-channels/nixos-unstable/ nixos

nix-channel --update


初次重建的时候可以手工指定镜像

nixos-rebuild switch --option substituters https://mirrors.ustc.edu.cn/nix-channels/store


7、这时候最小nix容器算可以用了,补上应用所需要的nix配置,加一行import就行

如果在这里把容器保存为模板,后续需要新容器可以节省掉上面这一堆步骤

当然也可以不用模板,只要还有一个nix的容器在跑,复制一个改下mountpoint路径,调整下nix文件后重建,自然就算声明了一个新应用的容器


8、nix配置


最小容器的nix文件示例如下,放在mountpoint所指的临时路径/nix-test/nixlxc下:

configuration.nix

{  pkgs,  modulesPath,  ...}: {

  imports = [

    (modulesPath + "/virtualisation/proxmox-lxc.nix")

    ./std.nix

  ];

  proxmoxLXC = {    manageNetwork = false;    privileged = true;  };

  environment.systemPackages = [

    #   pkgs.vim

  ];

  system.stateVersion = "25.11";

}


std.nix ,这个文件实际上大部分可以略过,不影响其他应用,纯个人习惯


{pkgs, ...}: {

  # 时区设置

  time.timeZone = "Asia/Shanghai"; # 设置时区为上海(中国标准时间)


  # zsh start


  programs.zsh = {

    enable = true; # Enable Zsh

    autosuggestions.enable = true;

    syntaxHighlighting.enable = true;


    shellAliases = {

      sc = "sudo systemctl";

      clean = "nix-collect-garbage --delete-older-than ";

      apply = "sudo nixos-rebuild switch";

      update = "nix-channel --update";

      up = "nix flake update";

    };


    ohMyZsh = {

      enable = true; # Enable Oh My Zsh

      plugins = ["git" "z"]; # Add desired plugins

      theme = "random"; # Set your preferred theme (default: robbyrussell)

    };

  };



  users.defaultUserShell = pkgs.zsh; # Set Zsh as the default shell

  environment.shells = [pkgs.zsh]; # Add Zsh to available shells


  # zsh end


  nix.settings = {

    substituters = [

      "https://mirrors.tuna.tsinghua.edu.cn/nix-channels/store"

      "https://mirrors.ustc.edu.cn/nix-channels/store"

    ];

   #    experimental-features = ["nix-command" "flakes"]; # 启用实验性功能:nix命令增强和flakes支持

  };


  environment.systemPackages = with pkgs; [

    vim

  ];

}


9、其他


也不知道为啥用pct enter的方式进入容器,设置的默认shell不生效,ssh或者pve网页端的console都正常;ai说是Pct enter是直接执行/bin/sh的缘故,我也就信了。


在命令里存一个 source /etc/set-environment && zsh 手工跑下也不费事



10、jellyfin

底子铺好后,跑jellyfin或者其他应用,基本也都是加几行配置重建的事情,提前把应用的config,应用的data,和应用需要的外部数据这三个路径组织好。按照无状态要求,这些一般都放在共享存储的环境里,可以在pve主机挂载好mountpoint到lxc里,也可以让lxc处理挂载。


nix文档有时候比较杂,好多介绍材料或者wiki页面里的配置项可能都依赖了home manager,比较干净的还是去mynixos查,比如 https://mynixos.com/search?q=jellyfin


jellyfin和smb挂载的配置,在configuration.nix添加import引入


Jellyfin.nix


{ pkgs, lib,config, ... }:

{

  services.jellyfin = {

    enable = true;

    openFirewall = true;

    configDir = "/jellyfin-config";

    dataDir = "/jellyfin-data";

  };


    environment.systemPackages = [

    pkgs.jellyfin

    pkgs.jellyfin-web

    pkgs.jellyfin-ffmpeg

  ];

}


Smb.nix ,其中有些选项用不上,可以根据smb共享服务端的配置调整


{ config, pkgs, ... }: {

  # SMB 挂载配置

  fileSystems."/mnt/dsm" = { device = "//192.168.1.8/dsm"; fsType =

    "cifs"; options = [

    "guest"

    "iocharset=utf8"

    "vers=3.0"

    "uid=568"

    "gid=568"

    "file_mode=0666"

    "dir_mode=0777"

#"x-systemd.automount"

  ];

  };

  # 创建符号链接

  system.activationScripts.create-symlinks = ''

    ln -sf /mnt/dsm/pub /mnt

 '';

      environment.systemPackages = with pkgs; [ cifs-utils ];

}


11、jellyfin的媒体路径错误


jellyfin数据库比较老或者经过环境迁移(尤其是docker和原生来来去去调整)的话,在编辑library设置的时候可能会报一个媒体库路径和数据库不符的错误,从而无法保存任何更改。


解决办法是手工修改数据库里保存的媒体库路径


10.10.7以前,数据库是jellyfin的data文件夹里的library.db,表名是TypedBaseItems

10.11以后,数据文件是data文件夹里的jellyfin.db,表名是BaseItems


用sqlite打开,


防止出错的话可以先查一下

Select path from TypedBaseItems WHERE type = 'MediaBrowser.Controller.Entities.CollectionFolder';


然后修改路径,path里的内容修改成data里root文件夹所在的正确路径就行。


UPDATE TypedBaseItems SET path = '/绝对路径/jellyfin-data/root/default/' || name WHERE type = 'MediaBrowser.Controller.Entities.CollectionFolder';