Making a Proxmox VM in to an LXC template

When using Proxmox one of the things that feels helpful to do is to configure the LXC (Linux Container) OSes exactly how you like them. We all have our preferences of what we want in the VMs, or even just setting up some basic software that you then want to duplicate a bunch of times. A WordPress server, for example.

It seems odd then that when you select in Proxmox to ‘Make as template’ it doesn’t *actually* do what you might expect. It makes that image just read-only so that it can then be the basis of linked clones for new VMs. That’s really not what is expected when dealing with LXC templates.

However, there is a way around that. I found on a blog post about how to use the vzdump tool to be able to take the image of an LXC container, make it a dump file and then move it to the /var/lib/vz/template/cache directory. From there you can select it ‘as a template’ when making a new container!

With that I made a script that will use the name of the container as part of the filename and prefix the output template with your host’s domain name – so that you know they’re yours.

The script is below:

#!/bin/bash

TEMPLATEDIR=/var/lib/vz/template/cache/
#TEMPLATEDIR=/ceph/data/Proxmox/template/cache

CONTAINERID=$1

if [ "$CONTAINERID" != "" ]; then
  CONFFILE=/tmp/vzdump_${CONTAINERID}.info
  LOGFILE=/tmp/vzdump_${CONTAINERID}.log
  
  pct config $CONTAINERID >$CONFFILE
  
  if [ "$?" = "0" ]; then 
    echo "Working on container $CONTAINERID"
    cat $CONFFILE

    pct set $CONTAINERID --delete net0

    echo "Archiving container $CONTAINERID ... "
    vzdump $CONTAINERID --mode stop --compress gzip --dumpdir $TEMPLATEDIR >$LOGFILE

    if [ "$?" = "0" ]; then
      cat $LOGFILE
      ARCFILE=`cat $LOGFILE | grep 'INFO: creating vzdump archive' | awk -F "'" {'print $2'}`
      echo "Archive created as $ARCFILE"

      # Move the log file out of the way
      ARCLOG=`echo $ARCFILE | sed -e "s/.tar.gz/.log/g"`
      rm -f $ARCLOG

      # Get info about the container
      CONTAINERHOST=`cat $CONFFILE | grep 'hostname:' | awk {'print $2'}`
      CONTAINERARCH=`cat $CONFFILE | grep 'arch:' | awk {'print $2'}`
      OURDOMAIN=`hostname -d`

      # Form new container filename
      EXTRA=""
      if [ "$2" != "" ]; then
        EXTRA="-${2}"
      fi
      NEWFILE="${OURDOMAIN}-${CONTAINERHOST}${EXTRA}-${CONTAINERARCH}.tar.gz"

      echo "Renaming to ${NEWFILE}"
      mv -f $ARCFILE ${TEMPLATEDIR}/${NEWFILE}
    else
      echo "Archive creation failed"
      cat $LOGFILE
    fi
  else
    echo "Unable to work on container $CONTAINERID"
    cat $CONFFILE
  fi

  rm -f $LOGFILE
  rm -f $CONFFILE
else
  echo "Specify a container ID"
fi

The big thing about it is that you remove the network device from the image before compressing, then when you make your new container nothing like that of the old system will remain (including the hostname). When creating a container you may also want to clean it up. I have a script for that too:

Run this with the ‘full’ argument (e.g. /root/bin/cleanup full) before making your VM template to save space:

#!/bin/bash

apt-get -y autoremove
apt-get clean

rm -f /var/log/dpkg.log

if [ "$1" = "full" ]; then
  rm -f /root/.bash_history
  rm -f /root/.lesshst
  rm -f /root/.viminfo

  find /var/lib/apt/lists -type f -exec rm \{\} \;

  journalctl --flush --rotate --vacuum-time=1s
  journalctl --user --flush --rotate --vacuum-time=1s
fi