So by default as far as I can tell, there's no way to directly run a script on boot with the default LXC container templates that are downloadable through the CT Templates section. No userdata, no cloudinit, etc support as far as I can tell. This lead me to have to come up with a kind of "hack" that isn't perfect but works for my specific scenario.
Why
So my solution was to use Proxmox hookscripts. Proxmox hookscripts are Perl(ewww lol) scripts that are run directly on the Proxmox hypervisor, NOT the LXC container. It's not super clear from the TF docs on this whether its run on the hypervisor or the container as it is part of the LXC container resource. You essentially have to read through the Proxmox docs and forums to get an understanding of it.
Solution
So my solution was to use a hookscript that is able to execute on the container itself through the hypervisor using features within proxmox's CLI tooling. Below is an example of a hookscript I wrote:
#!/usr/bin/perl
use strict;
use warnings;
my $vmid = shift;
my $phase = shift;
my $target_host = 'hv02'; # Replace with the hostname of the target Proxmox host that holds the ansible scripts
my $container_id_on_target_host = '108'; # Replace with the container ID
if ($phase eq 'post-start') {
# First, install openssh-server inside the container that has just been started
my $install_cmd = 'dnf install -y openssh-server'; # This installs openssh-server as its not installed on the almalinux 9 LXC CT image that is provided by Proxmox
my $sed_cmd = "sed -i.bak 's/^PermitRootLogin \\(prohibit-password\\|no\\)/PermitRootLogin yes/' /etc/ssh/sshd_config"; # this edits the sshd_conf file to allow root login, its within my security tolerance for the scenario this server serves.
my $restart_sshd = 'systemctl restart sshd'; # interestingly this doesn't work. Why? I don't know. when the VM boots it shows that the service isnt actually started.
my $start_sshd = 'systemctl enable sshd';
my $full_cmd = "pct exec $vmid -- bash -c '$install_cmd && $sed_cmd && $restart_sshd && $start_sshd'"; # was running into string interp issues so split commands into vars which has the perk of making the code more readable
# Execute the installation command(this gets executed on the hypervisor that this script is run on. This should be the same hypervisor that you want the LXC container created on)
system($full_cmd);
sleep(5); # wait to prevent screwing up of proxmox's locking system
my $reboot_ct = "pct exec $vmid -- bash -c 'sudo shutdown -r now'"; # kind of a hack to get around the fact that restarting ssh through the restart command earlier in this script doesn't work despite being enabled in systemd.
system($reboot_ct);
# Next, retrieve the IP address of the container's eth0 interface
my $get_ip_cmd = "pct exec $vmid -- ip -4 addr show eth0 | grep -oP '(?<=inet\\s)\\d+(\\.\\d+){3}'";
my $container_ip = `$get_ip_cmd`;
chomp $container_ip; # Remove any trailing newline
# If the IP address was successfully retrieved, run ssh-keygen -R on the target host's container. This is because I spin up and down test containers quite often but I get irritated at having to remove the known key every time is annoying to me when I just want Ansible to work without extra effort. It's within my security tolerances but feel free to remove from script it it isn't within yours.
# This command ssh's to the hv that holds the container that does ansible stuff.
if ($container_ip) {
my $ssh_cmd = "ssh root\@$target_host 'pct exec $container_id_on_target_host -- ssh-keygen -R $container_ip'";
system($ssh_cmd);
} else {
warn "Failed to get the IP address of the container with ID $vmid";
}
}
exit(0);
Save the script to: /var/lib/vz/snippets/install-ssh.pl although feel free to rename the script itself, but thats the directory. Make sure its on the host you want the container to be on.
Terraform Example:
module "container-name-here" {
source = "../modules/lxc-vm"
hostname = "put hostname here"
cores = 4
memory = "4096"
swap = "2048"
ostemplate = "local:vztmpl/almalinux-9-default_20221108_amd64.tar.xz"
hookscript = "local:snippets/install-ssh.pl" # the script we referred to earlier
container_password = var.container_password # please, please make sure to not actually put password in code. use CI/CD for this..
target_node = "hv03" # which hypervisor you want this lxc container to be put.
ssh_public_keys = <<-EOT
<put your public key here>
EOT
unprivileged = true
onboot = true
start = true
features_nesting = true # turn off if you don't need
features_keyctl = true # turn off if you don't need
features_fuse = true # turn off if you don't need
rootfs_storage = "local"
rootfs_size = "40G"
network_name = "eth0"
network_bridge = "vmbr0"
network_ip = "x.x.x.x/24"
network_gw = "x.x.x.x.254"
network_hwaddr = "mac address here"
}
Deploying LXC Containers in Proxmox using Terraform and Ansible
Problem
So by default as far as I can tell, there's no way to directly run a script on boot with the default LXC container templates that are downloadable through the CT Templates section. No userdata, no cloudinit, etc support as far as I can tell. This lead me to have to come up with a kind of "hack" that isn't perfect but works for my specific scenario.
Why
So my solution was to use Proxmox hookscripts. Proxmox hookscripts are Perl(ewww lol) scripts that are run directly on the Proxmox hypervisor, NOT the LXC container. It's not super clear from the TF docs on this whether its run on the hypervisor or the container as it is part of the LXC container resource. You essentially have to read through the Proxmox docs and forums to get an understanding of it.
Solution
So my solution was to use a hookscript that is able to execute on the container itself through the hypervisor using features within proxmox's CLI tooling. Below is an example of a hookscript I wrote:
Save the script to: /var/lib/vz/snippets/install-ssh.pl although feel free to rename the script itself, but thats the directory. Make sure its on the host you want the container to be on.
Terraform Example: