Every script in the Proxmox VE Helper Scripts project follows consistent patterns for maintainability and reliability. Understanding these patterns helps you customize scripts or troubleshoot issues.
Container Script Anatomy
Let’s examine a complete container script structure using real examples from the codebase.
Host Script (ct/*.sh)
The script that runs on your Proxmox VE host to create containers.
Structure Overview
Real Example: ct/2fauth.sh
Real Example: ct/docker.sh
#!/usr/bin/env bash
# 1. HEADER: Shebang and metadata
# 2. SOURCE DEPENDENCIES: Load build.func
source <( curl -fsSL https://raw.githubusercontent.com/.../misc/build.func)
# 3. METADATA: Copyright, license, source
# Copyright (c) 2021-2026 community-scripts ORG
# License: MIT
# 4. APP CONFIGURATION: Define application defaults
APP = "Application Name"
var_tags = "${ var_tags :- tag1 ; tag2 }"
var_cpu = "${ var_cpu :- 2 }"
var_ram = "${ var_ram :- 1024 }"
var_disk = "${ var_disk :- 10 }"
var_os = "${ var_os :- debian }"
var_version = "${ var_version :- 13 }"
var_unprivileged = "${ var_unprivileged :- 1 }"
# 5. INITIALIZATION: Setup environment
header_info " $APP "
variables
color
catch_errors
# 6. UPDATE FUNCTION: Logic for existing containers
function update_script () {
# Update implementation
}
# 7. EXECUTION: Start build process
start
build_container
description
# 8. COMPLETION: Display success message
msg_ok "Completed successfully!\n"
Configuration Variables
Every host script defines these standard variables:
Human-readable application name (e.g., “2FAuth”, “Docker”, “Pi-hole”)
Semicolon-separated tags for categorization (e.g., “2fa;authenticator”)
CPU cores allocated to container. Uses ${var_cpu:-1} pattern for defaults.
RAM in MB (e.g., 512, 1024, 2048)
Operating system (debian, ubuntu, alpine)
OS version (13 for Debian 13, 24.04 for Ubuntu, etc.)
Container type: 1=unprivileged (secure), 0=privileged
The ${var_name:-default} syntax means: “Use environment variable var_name if set, otherwise use default”. This enables user customization while providing sensible defaults.
Build Process Flow
When build_container is called, this happens:
Install Script (install/*-install.sh)
The script that executes inside the container to install the application.
Install Script Structure
Structure Overview
Real Example: install/2fauth-install.sh
Real Example: install/docker-install.sh
#!/usr/bin/env bash
# 1. METADATA: Copyright and license
# 2. SOURCE DEPENDENCIES: Load install.func
source /dev/stdin <<< " $FUNCTIONS_FILE_PATH "
# 3. INITIALIZATION: Setup container environment
color
verb_ip6
catch_errors
setting_up_container
network_check
update_os
# 4. DEPENDENCIES: Install required packages
msg_info "Installing Dependencies"
$STD apt install -y package1 package2
msg_ok "Installed Dependencies"
# 5. APPLICATION SETUP: Install main application
# - Setup databases
# - Configure services
# - Download application
# 6. SERVICE CONFIGURATION: Configure systemd/init
msg_info "Configure Service"
# Create config files
msg_ok "Configured Service"
# 7. FINALIZATION: Cleanup and prepare
motd_ssh
customize
cleanup_lxc
Key Helper Functions
Install scripts use these standard functions from install.func and core.func:
color - Sets up color codes for outputcolor # Enables colored terminal output
verb_ip6 - Configures IPv6 and verbose modeverb_ip6 # Sets up IPv6 based on user selection
catch_errors - Enables error trappingcatch_errors # Trap errors and cleanup on failure
setting_up_container - Initial container setupsetting_up_container # Display setup banner
network_check - Verifies internet connectivitynetwork_check
# Tests DNS resolution and internet access
# Exits if network unavailable
update_os - Updates package listsupdate_os
# Runs apt update (Debian/Ubuntu)
# or apk update (Alpine)
setup_php - Installs and configures PHPexport PHP_VERSION = "8.4"
PHP_FPM = "YES" setup_php
# Installs PHP 8.4 with FPM support
setup_mariadb - Installs MariaDB serversetup_mariadb
# Installs and secures MariaDB
setup_mariadb_db - Creates database and userMARIADB_DB_NAME = "myapp_db" MARIADB_DB_USER = "myapp" setup_mariadb_db
# Creates database, user, and random password
# Password stored in $MARIADB_DB_PASS
setup_composer - Installs Composersetup_composer
# Installs latest Composer globally
fetch_and_deploy_gh_release - Downloads GitHub releasesfetch_and_deploy_gh_release "destination" "owner/repo" "type"
# Types: tarball, zipball, binary
# Extracts to /opt/destination
get_latest_github_release - Gets latest release tagVERSION = $( get_latest_github_release "moby/moby" )
# Returns: "v27.0.3" (example)
motd_ssh - Configures message of the daymotd_ssh
# Sets up /etc/motd with container info
# Configures SSH if enabled
customize - Applies user customizationscustomize
# Runs user-defined customization hooks
cleanup_lxc - Final cleanupcleanup_lxc
# Clears package cache
# Removes temporary files
Update Script Pattern
Every host script includes an update_script() function for updating existing containers:
Update Script Structure
function update_script () {
# 1. HEADER
header_info
check_container_storage
check_container_resources
# 2. VALIDATION: Check if app is installed
if [[ ! -d "/opt/myapp" ]]; then
msg_error "No ${ APP } Installation Found!"
exit
fi
# 3. VERSION CHECK: Only update if new version available
if check_for_gh_release "myapp" "owner/repo" ; then
# 4. SYSTEM UPDATE
$STD apt update
$STD apt -y upgrade
# 5. BACKUP: Create backup before updating
msg_info "Creating Backup"
mv "/opt/myapp" "/opt/myapp-backup"
msg_ok "Backup Created"
# 6. UPDATE APPLICATION
fetch_and_deploy_gh_release "myapp" "owner/repo" "tarball"
# 7. RESTORE DATA
mv "/opt/myapp-backup/config" "/opt/myapp/config"
mv "/opt/myapp-backup/data" "/opt/myapp/data"
# 8. RESTART SERVICES
$STD systemctl restart myapp
msg_ok "Updated successfully!"
fi
exit
}
Real Update Examples
Simple Update (2fauth)
Complex Update (Docker)
function update_script () {
header_info
check_container_storage
check_container_resources
if [[ ! -d "/opt/2fauth" ]]; then
msg_error "No ${ APP } Installation Found!"
exit
fi
setup_mariadb # Ensure DB helpers available
if check_for_gh_release "2fauth" "Bubka/2FAuth" ; then
$STD apt update
$STD apt -y upgrade
msg_info "Creating Backup"
mv "/opt/2fauth" "/opt/2fauth-backup"
msg_ok "Backup Created"
# Upgrade PHP if needed
if ! dpkg -l | grep -q 'php8.4' ; then
PHP_VERSION = "8.4" PHP_FPM = "YES" setup_php
sed -i 's/php8\.[0-9]/php8.4/g' /etc/nginx/conf.d/2fauth.conf
fi
fetch_and_deploy_gh_release "2fauth" "Bubka/2FAuth" "tarball"
setup_composer
# Restore data
mv "/opt/2fauth-backup/.env" "/opt/2fauth/.env"
mv "/opt/2fauth-backup/storage" "/opt/2fauth/storage"
cd "/opt/2fauth" || return
chown -R www-data: "/opt/2fauth"
export COMPOSER_ALLOW_SUPERUSER = 1
$STD composer install --no-dev --prefer-dist
php artisan 2fauth:install
$STD systemctl restart nginx
msg_ok "Updated successfully!"
fi
exit
}
Common Patterns
The $STD Variable
The $STD variable controls command output visibility:
# When VERBOSE=no (default)
$STD apt install nginx
# Output: Hidden (redirected to /dev/null)
# When VERBOSE=yes
$STD apt install nginx
# Output: Visible (shows all apt output)
# Implementation (from core.func)
if [[ " $VERBOSE " == "yes" ]]; then
STD = "" # Show output
else
STD = "&>/dev/null" # Hide output
fi
Use $STD prefix for all commands that produce verbose output to respect user’s verbosity setting.
Error Handling Pattern
# Check if directory exists before proceeding
if [[ ! -d "/opt/myapp" ]]; then
msg_error "No ${ APP } Installation Found!"
exit
fi
# Check if service is running
if ! systemctl is-active --quiet myapp ; then
msg_warn "Service is not running, starting it..."
systemctl start myapp
fi
# Validate command success
if ! command -v docker & > /dev/null; then
msg_error "Docker is not installed!"
exit 1
fi
Standard message functions provide consistent UI:
msg_info "Installing Dependencies" # Blue info message
$STD apt install -y nginx
msg_ok "Installed Dependencies" # Green success message
msg_warn "Configuration file missing, using defaults" # Yellow warning
msg_error "Critical error occurred!" # Red error message
Configuration File Creation
Use heredoc for multi-line config files:
msg_info "Configure Service"
cat << EOF > /etc/nginx/conf.d/myapp.conf
server {
listen 80;
server_name $LOCAL_IP ;
root /opt/myapp/public;
index index.php index.html;
location / {
try_files \$ uri \$ uri/ /index.php? \$ query_string;
}
location ~ \.php \$ {
fastcgi_pass unix:/var/run/php/php${ PHP_VERSION }-fpm.sock;
include fastcgi_params;
}
}
EOF
systemctl reload nginx
msg_ok "Configured Service"
Remember to escape $ as \$ inside heredocs when you want literal dollar signs in the config file.
File Naming Conventions
Type Pattern Example Container Script ct/<app>.shct/2fauth.sh, ct/docker.shInstall Script install/<app>-install.shinstall/2fauth-install.shVM Script vm/<os>-vm.shvm/haos-vm.sh, vm/opnsense-vm.shFunction Library misc/<name>.funcmisc/build.func, misc/core.funcApp Name (NSAPP) Lowercase, no spaces 2fauth, docker, uptimekuma
Variable Naming Conventions
# User-configurable variables (prefixed with var_)
var_cpu = 2
var_ram = 1024
var_disk = 10
var_hostname = "myapp"
var_unprivileged = 1
# Internal variables (UPPERCASE)
APP = "MyApp"
NSAPP = "myapp" # Normalized app name
CT_ID = 100
CORE_COUNT = 2
RAM_SIZE = 1024
DISK_SIZE = "10"
# Function-local variables (lowercase)
local file = "/path/to/file"
local version = "1.2.3"
Best Practices
Always Source Dependencies First
#!/usr/bin/env bash
source <( curl -fsSL .../misc/build.func) # First line after shebang
# Then define variables
APP = "MyApp"
var_cpu = "${ var_cpu :- 2 }"
Use Default Values for All Variables
# ✅ GOOD: Provides fallback
var_cpu = "${ var_cpu :- 2 }"
var_ram = "${ var_ram :- 1024 }"
# ❌ BAD: No fallback if not set
var_cpu = " $var_cpu "
Always Validate Installation Before Updates
function update_script () {
# ✅ GOOD: Check installation exists
if [[ ! -d "/opt/myapp" ]]; then
msg_error "No ${ APP } Installation Found!"
exit
fi
# ... update logic
}
Create Backups Before Updates
msg_info "Creating Backup"
mv "/opt/myapp" "/opt/myapp-backup"
cp "/etc/myapp/config.yml" "/etc/myapp/config.yml.bak"
msg_ok "Backup Created"
# ... perform update
# Restore data
mv "/opt/myapp-backup/data" "/opt/myapp/data"
# Set ownership to web server user
chown -R www-data:www-data /opt/myapp
# Set appropriate permissions
chmod -R 755 /opt/myapp
# Protect sensitive files
chmod 600 /opt/myapp/.env
Debugging Tips
Enable Verbose Mode
# Set before running script
export var_verbose = yes
bash -c "$( wget -qLO - https://github.com/.../ct/myapp.sh)"
# Or in advanced settings menu, select verbose option
Check Build Logs
# On Proxmox host
tail -f /tmp/create-lxc- * .log
# Find all build logs
ls -lht /tmp/create-lxc- * .log | head
Check Install Logs Inside Container
# Enter container
pct enter < CTI D >
# Check system logs
journalctl -xe
# Check service status
systemctl status myapp
Common Issues
Network connectivity issues
# Inside container, test connectivity
ping -c 3 8.8.8.8
ping -c 3 google.com
# Check DNS
cat /etc/resolv.conf
# Check if unprivileged container can access files
ls -la /opt/myapp
# Fix ownership
chown -R www-data:www-data /opt/myapp
# Check service status
systemctl status myapp
# View logs
journalctl -u myapp -n 50
# Check config syntax
nginx -t # For nginx
php -m # For PHP modules
Next Steps
Architecture Learn about the overall system architecture
Containers vs VMs Understand when to use containers or VMs