Container scripts (ct/AppName.sh) are entry points for creating LXC containers with specific applications pre-installed on Proxmox VE. These scripts define container defaults, orchestrate the build process, and provide update mechanisms.
Overview
Execution Flow
Proxmox Host
↓
ct/AppName.sh sourced (runs as root on host)
↓
build.func: Creates LXC container + runs install script inside
↓
install/AppName-install.sh (runs inside container)
↓
Container ready with app installed
Key Integration Points
- build.func - Main orchestrator (container creation, storage, variable management)
- install.func - Container-specific setup (OS update, package management)
- tools.func - Tool installation helpers (repositories, GitHub releases)
- core.func - UI/messaging functions (colors, spinners, validation)
- error_handler.func - Error handling and signal management
Container Creation Workflow
Set APP and defaults
Define application metadata and default container resources (CPU, RAM, disk, OS).
Display header
Show ASCII art header using header_info() function.
Parse arguments
Process command-line arguments and load build.func using variables() function.
Setup colors
Initialize ANSI color codes with color() function.
Initialize error handling
Setup trap handlers using catch_errors() function.
Show installation menu
Display 5 installation modes:
- Default (Quick setup)
- Advanced (19-step wizard)
- User Defaults
- App Defaults
- Settings Menu
Build container
Create LXC container and execute install script inside.
Set description
Add container description visible in Proxmox UI.
Display success message
Show access URL and completion status.
Complete Script Template
Basic Structure
#!/usr/bin/env bash
# Copyright (c) 2021-2026 community-scripts ORG
# Author: YourUsername
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://github.com/example/project
# Import main orchestrator
source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVE/raw/branch/main/misc/build.func)
# Application Configuration
APP="ApplicationName"
var_tags="tag1;tag2;tag3" # Max 3-4 tags, semicolon-separated
# Container Resources
var_cpu="2" # CPU cores
var_ram="2048" # RAM in MB
var_disk="10" # Disk in GB
# Container Type & OS
var_os="debian" # Options: alpine, debian, ubuntu
var_version="12" # Alpine: 3.20+, Debian: 11-13, Ubuntu: 20.04+
var_unprivileged="1" # 1=unprivileged (secure), 0=privileged
# Display header and initialize
header_info "$APP"
variables
color
catch_errors
# Update function (runs when script is executed on existing container)
function update_script() {
header_info
check_container_storage
check_container_resources
# Verify app is installed
if [[ ! -d /opt/appname ]]; then
msg_error "No ${APP} Installation Found!"
exit
fi
# Get latest version from GitHub
RELEASE=$(curl -fsSL https://api.github.com/repos/user/repo/releases/latest | \
grep "tag_name" | awk '{print substr($2, 2, length($2)-3)}')
# Compare with saved version
if [[ ! -f /opt/${APP}_version.txt ]] || [[ "${RELEASE}" != "$(cat /opt/${APP}_version.txt)" ]]; then
msg_info "Updating ${APP} to v${RELEASE}"
# Update logic here
cd /opt
# Download, install, configure...
# Save new version
echo "${RELEASE}" > /opt/${APP}_version.txt
msg_ok "Updated ${APP} to v${RELEASE}"
else
msg_ok "No update required. ${APP} is already at v${RELEASE}."
fi
exit
}
# Launch container creation
start
build_container
description
# Display success message
msg_ok "Completed successfully!\n"
echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}"
echo -e "${INFO}${YW} Access it using the following URL:${CL}"
echo -e "${TAB}${GATEWAY}${BGN}http://${IP}:8080${CL}"
Real-World Examples
#!/usr/bin/env bash
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)
# Copyright (c) 2021-2026 tteck
# Author: tteck (tteckster)
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://pi-hole.net/
APP="Pihole"
var_tags="${var_tags:-adblock}"
var_cpu="${var_cpu:-1}"
var_ram="${var_ram:-512}"
var_disk="${var_disk:-2}"
var_os="${var_os:-debian}"
var_version="${var_version:-13}"
var_unprivileged="${var_unprivileged:-1}"
header_info "$APP"
variables
color
catch_errors
function update_script() {
header_info
check_container_storage
check_container_resources
if [[ ! -d /etc/pihole ]]; then
msg_error "No ${APP} Installation Found!"
exit
fi
msg_info "Updating PiHole"
set +e
$STD apt update
$STD apt upgrade -y
/usr/local/bin/pihole -up
msg_ok "Updated PiHole"
msg_ok "Updated successfully!"
exit
}
start
build_container
description
msg_ok "Completed successfully!\n"
echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}"
echo -e "${INFO}${YW} Access it using the following URL:${CL}"
echo -e "${TAB}${GATEWAY}${BGN}http://${IP}/admin${CL}"
#!/usr/bin/env bash
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)
# Copyright (c) 2021-2026 tteck
# Author: tteck (tteckster)
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://www.docker.com/
APP="Docker"
var_tags="${var_tags:-docker}"
var_cpu="${var_cpu:-2}"
var_ram="${var_ram:-2048}"
var_disk="${var_disk:-4}"
var_os="${var_os:-debian}"
var_version="${var_version:-13}"
var_unprivileged="${var_unprivileged:-1}"
header_info "$APP"
variables
color
catch_errors
function update_script() {
header_info
check_container_storage
check_container_resources
msg_info "Updating base system"
$STD apt update
$STD apt upgrade -y
msg_ok "Base system updated"
msg_info "Updating Docker Engine"
$STD apt install --only-upgrade -y docker-ce docker-ce-cli containerd.io docker-compose-plugin docker-buildx-plugin
msg_ok "Docker Engine updated"
if docker ps -a --format '{{.Image}}' | grep -q '^portainer/portainer-ce:latest$'; then
msg_info "Updating Portainer"
$STD docker pull portainer/portainer-ce:latest
$STD docker stop portainer
$STD docker rm portainer
$STD docker volume create portainer_data >/dev/null 2>&1
$STD docker run -d \
-p 8000:8000 \
-p 9443:9443 \
--name=portainer \
--restart=always \
-v /var/run/docker.sock:/var/run/docker.sock \
-v portainer_data:/data \
portainer/portainer-ce:latest
msg_ok "Updated Portainer"
fi
msg_ok "Updated successfully!"
exit
}
start
build_container
description
msg_ok "Completed successfully!\n"
echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}"
echo -e "${INFO}${YW} If you installed Portainer, access it at the following URL:${CL}"
echo -e "${TAB}${GATEWAY}${BGN}https://${IP}:9443${CL}"
Variable Naming Convention
Variables exposed to users must use the var_* prefix (e.g., var_cpu, var_hostname, var_ssh). Internal variables should use lowercase names (e.g., container_id, app_version).
Available Variables
| Variable | Description | Example |
|---|
var_cpu | CPU cores | "2" |
var_ram | RAM in MB | "2048" |
var_disk | Disk size in GB | "10" |
var_os | Operating system | "debian", "alpine", "ubuntu" |
var_version | OS version | "12", "3.20", "22.04" |
var_unprivileged | Container type | "1" (unprivileged), "0" (privileged) |
var_tags | Application tags | "web;dashboard;monitoring" |
Default Values Precedence
Priority 1 (Highest): Environment Variables (var_cpu, var_ram, etc.)
Priority 2: App-Specific Defaults (/defaults/AppName.vars)
Priority 3: User Global Defaults (/default.vars)
Priority 4 (Lowest): Built-in Defaults (in build.func)
Core Functions
Displays ASCII art header for the application.
variables()
Initializes container variables, loads user arguments, and sets up orchestration.
start()
Launches the container creation menu with 5 installation modes:
- Default Installation - Quick setup with predefined settings
- Advanced Installation - 19-step wizard with full control
- User Defaults - Load ~/.community-scripts/default.vars
- App Defaults - Load /defaults/AppName.vars
- Settings Menu - Interactive mode selection
build_container()
Main orchestrator for LXC container creation:
- Validates all variables
- Creates LXC container via
pct create
- Executes
install/AppName-install.sh inside container
- Monitors installation progress
- Handles errors and rollback on failure
description()
Sets container description/notes visible in Proxmox UI.
Update Function Best Practices
Always implement an update_script() function to enable easy updates for existing containers.
function update_script() {
header_info
# Always start with these checks
check_container_storage
check_container_resources
# Verify app is installed
if [[ ! -d /opt/appname ]]; then
msg_error "No ${APP} Installation Found!"
exit
fi
# Get latest version from GitHub
RELEASE=$(curl -fsSL https://api.github.com/repos/user/repo/releases/latest | \
grep "tag_name" | awk '{print substr($2, 2, length($2)-3)}')
# Compare with saved version
if [[ ! -f /opt/${APP}_version.txt ]] || [[ "${RELEASE}" != "$(cat /opt/${APP}_version.txt)" ]]; then
msg_info "Updating ${APP} to v${RELEASE}"
# Backup user data (optional but recommended)
cp -r /opt/appname /opt/appname-backup
# Perform update
cd /opt
wget -q "https://github.com/user/repo/releases/download/v${RELEASE}/app-${RELEASE}.tar.gz"
tar -xzf app-${RELEASE}.tar.gz
# Restore user data if needed
cp /opt/appname-backup/config/* /opt/appname/config/
# Cleanup
rm -rf app-${RELEASE}.tar.gz /opt/appname-backup
# Save new version
echo "${RELEASE}" > /opt/${APP}_version.txt
msg_ok "Updated ${APP} to v${RELEASE}"
else
msg_ok "No update required. ${APP} is already at v${RELEASE}."
fi
exit
}
Troubleshooting
Container Creation Fails
Symptom: pct create exits with error code 209
Solution:
# Check existing containers
pct list | grep CTID
# Remove conflicting container
pct destroy CTID
# Retry ct/AppName.sh
Update Function Doesn’t Detect New Version
Debug:
# Check version file
cat /opt/AppName_version.txt
# Test GitHub API
curl -fsSL https://api.github.com/repos/user/repo/releases/latest | grep tag_name
Best Practices
DO:
- Use meaningful defaults appropriate for your application
- Implement version tracking with
/opt/${APP}_version.txt
- Handle edge cases in the update function
- Use proper messaging with
msg_info/msg_ok/msg_error
- Test with both default and advanced installation modes
- Use
${var_cpu:-2} syntax to allow environment variable overrides
DON’T:
- Hardcode versions - always fetch latest from GitHub API
- Use custom color codes - use built-in variables from
color() function
- Forget error handling in critical sections
- Leave temporary files or backups
- Use privileged containers unless absolutely necessary
Contribution Checklist
Before submitting a PR: