Skip to main content
Unattended deployments enable fully automated container provisioning without manual interaction. Perfect for CI/CD pipelines, Infrastructure as Code (IaC), and batch deployments.

Quick Start

The simplest unattended deployment:
var_hostname=myserver bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/debian.sh)"

Prerequisites

1

Proxmox VE Access

Verify root access and Proxmox version:
whoami  # Should return: root
pveversion  # Should be 8.0+ or 9.0-9.1
2

Network Connectivity

Test GitHub and internet access:
curl -I https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/debian.sh
ping -c 1 1.1.1.1
3

Available Storage

Verify storage availability:
pvesm status
df -h

Deployment Methods

Environment Variables

Complexity: Low
Flexibility: High
Best for: Quick one-off deployments

App Defaults

Complexity: Low
Flexibility: Medium
Best for: Repeating identical deployments

Shell Scripts

Complexity: Medium
Flexibility: High
Best for: Batch operations and automation

Ansible/Terraform

Complexity: High
Flexibility: Very High
Best for: Enterprise Infrastructure as Code

Single Container Deployment

Basic Example

#!/bin/bash
# deploy-single.sh - Deploy a single container with full configuration

var_unprivileged=1 \
var_cpu=4 \
var_ram=4096 \
var_disk=30 \
var_hostname=production-app \
var_os=debian \
var_version=13 \
var_brg=vmbr0 \
var_net=dhcp \
var_ipv6_method=none \
var_ssh=yes \
var_ssh_authorized_key="ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQ... admin@workstation" \
var_nesting=1 \
var_tags=production,automated \
var_protection=yes \
var_verbose=no \
  bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/debian.sh)"

echo "✓ Container deployed successfully"

Automatic IP Assignment

Use IP range scanning to automatically assign the first available IP:
#!/bin/bash
# deploy-with-ip-scan.sh - Auto-assign first free IP from range

var_unprivileged=1 \
var_cpu=4 \
var_ram=4096 \
var_hostname=web-server \
var_net=192.168.1.100/24-192.168.1.150/24 \
var_gateway=192.168.1.1 \
  bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/debian.sh)"

# The script will:
# 1. Ping 192.168.1.100 - if responds, skip
# 2. Ping 192.168.1.101 - if responds, skip
# 3. Continue until first IP that doesn't respond
# 4. Assign that IP to the container
IP range format is START_IP/CIDR-END_IP/CIDR. Both sides must include the same CIDR notation.

Using Saved Defaults

Run once interactively to save your configuration:
bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/pihole.sh)"
# Select "Advanced Settings" → Configure → Save as "App Defaults"

Batch Deployments

Simple Loop

Deploy multiple containers sequentially:
#!/bin/bash
# batch-deploy-simple.sh

apps=("debian" "ubuntu" "alpine")

for app in "${apps[@]}"; do
  echo "Deploying $app..."
  var_hostname="$app-server" \
  var_cpu=2 \
  var_ram=2048 \
    bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/${app}.sh)"
  
  echo "✓ $app deployed"
  sleep 5  # Wait between deployments
done

Advanced Configuration Array

Deploy multiple containers with individual configurations:
#!/bin/bash
# batch-deploy-advanced.sh

declare -A CONTAINERS=(
  ["pihole"]="2:1024:8:vmbr0:dns,network"
  ["homeassistant"]="4:4096:20:vmbr1:automation,ha"
  ["docker"]="6:8192:50:vmbr0:containers,docker"
)

for app in "${!CONTAINERS[@]}"; do
  # Parse configuration
  IFS=':' read -r cpu ram disk bridge tags <<< "${CONTAINERS[$app]}"
  
  echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
  echo "Deploying: $app"
  echo "  CPU: $cpu cores"
  echo "  RAM: $ram MB"
  echo "  Disk: $disk GB"
  echo "  Bridge: $bridge"
  echo "  Tags: $tags"
  echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
  
  # Deploy container
  var_unprivileged=1 \
  var_cpu="$cpu" \
  var_ram="$ram" \
  var_disk="$disk" \
  var_hostname="$app" \
  var_brg="$bridge" \
  var_net=dhcp \
  var_ipv6_method=none \
  var_ssh=yes \
  var_tags="$tags,automated" \
    bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/${app}.sh)" 2>&1 | tee "deploy-${app}.log"
  
  if [ $? -eq 0 ]; then
    echo "✓ $app deployed successfully"
  else
    echo "✗ $app deployment failed - check deploy-${app}.log"
  fi
  
  sleep 5
done

echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "Batch deployment complete!"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"

Parallel Deployment

Deploy multiple containers simultaneously:
#!/bin/bash
# parallel-deploy.sh

deploy_container() {
  local app="$1"
  local cpu="$2"
  local ram="$3"
  local disk="$4"
  
  echo "[$app] Starting deployment..."
  var_cpu="$cpu" \
  var_ram="$ram" \
  var_disk="$disk" \
  var_hostname="$app" \
  var_net=dhcp \
    bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/${app}.sh)" \
    &> "deploy-${app}.log"
  
  echo "[$app] ✓ Completed"
}

# Export function for parallel execution
export -f deploy_container

# Deploy in parallel (max 3 at a time)
parallel -j 3 deploy_container ::: \
  "debian 2 2048 10" \
  "ubuntu 2 2048 10" \
  "alpine 1 1024 5"

echo "All deployments complete!"

Infrastructure as Code

Ansible Playbook

---
# playbook-proxmox.yml
- name: Deploy ProxmoxVE Containers
  hosts: proxmox_hosts
  become: yes
  tasks:
    - name: Deploy Debian Container
      shell: |
        var_unprivileged=1 \
        var_cpu=2 \
        var_ram=2048 \
        var_disk=10 \
        var_hostname=debian-{{ inventory_hostname }} \
        var_net=dhcp \
        var_ssh=yes \
        var_tags=ansible,automated \
        bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/debian.sh)"
      args:
        executable: /bin/bash
      register: deploy_result
    
    - name: Display deployment result
      debug:
        var: deploy_result.stdout_lines

Terraform

# main.tf
terraform {
  required_providers {
    proxmox = {
      source = "telmate/proxmox"
      version = "2.9.14"
    }
  }
}

provider "proxmox" {
  pm_api_url = "https://proxmox.example.com:8006/api2/json"
  pm_api_token_id = "terraform@pam!terraform"
  pm_api_token_secret = var.proxmox_token
}

resource "null_resource" "deploy_container" {
  for_each = var.containers
  
  provisioner "remote-exec" {
    inline = [
      "var_unprivileged=1",
      "var_cpu=${each.value.cpu}",
      "var_ram=${each.value.ram}",
      "var_disk=${each.value.disk}",
      "var_hostname=${each.key}",
      "var_net=dhcp",
      "bash -c \"$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/${each.value.template}.sh)\""
    ]
    
    connection {
      type = "ssh"
      host = var.proxmox_host
      user = "root"
      private_key = file("~/.ssh/id_rsa")
    }
  }
}

variable "containers" {
  type = map(object({
    template = string
    cpu = number
    ram = number
    disk = number
  }))
  
  default = {
    "pihole" = {
      template = "pihole"
      cpu = 2
      ram = 1024
      disk = 8
    }
    "homeassistant" = {
      template = "homeassistant"
      cpu = 4
      ram = 4096
      disk = 20
    }
  }
}

CI/CD Integration

GitHub Actions

# .github/workflows/deploy-container.yml
name: Deploy Container to Proxmox

on:
  push:
    branches: [main]
  workflow_dispatch:
    inputs:
      container_type:
        description: 'Container type to deploy'
        required: true
        type: choice
        options:
          - debian
          - ubuntu
          - docker

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Deploy to Proxmox
        uses: appleboy/[email protected]
        with:
          host: ${{ secrets.PROXMOX_HOST }}
          username: root
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          script: |
            var_unprivileged=1 \
            var_cpu=4 \
            var_ram=4096 \
            var_disk=30 \
            var_hostname=${{ github.event.inputs.container_type }}-ci \
            var_net=dhcp \
            var_ssh=yes \
            var_tags=ci-cd,automated \
            bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/${{ github.event.inputs.container_type }}.sh)"
      
      - name: Notify deployment status
        if: success()
        run: echo "✓ Container deployed successfully"

GitLab CI

# .gitlab-ci.yml
stages:
  - deploy

deploy_container:
  stage: deploy
  image: alpine:latest
  before_script:
    - apk add --no-cache openssh-client curl bash
    - eval $(ssh-agent -s)
    - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
    - mkdir -p ~/.ssh
    - chmod 700 ~/.ssh
    - ssh-keyscan $PROXMOX_HOST >> ~/.ssh/known_hosts
  script:
    - |
      ssh root@$PROXMOX_HOST << 'EOF'
        var_unprivileged=1 \
        var_cpu=4 \
        var_ram=4096 \
        var_disk=30 \
        var_hostname=gitlab-ci-container \
        var_net=dhcp \
        var_tags=gitlab-ci,automated \
        bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/debian.sh)"
      EOF
  only:
    - main
  when: manual

Error Handling

Deployment with Retry Logic

#!/bin/bash
# deploy-with-verification.sh

APP="debian"
HOSTNAME="production-server"
MAX_RETRIES=3
RETRY_COUNT=0

deploy_container() {
  echo "Attempting deployment (Try $((RETRY_COUNT + 1))/$MAX_RETRIES)..."
  
  var_unprivileged=1 \
  var_cpu=4 \
  var_ram=4096 \
  var_disk=30 \
  var_hostname="$HOSTNAME" \
  var_net=dhcp \
  var_ssh=yes \
    bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/${APP}.sh)" 2>&1 | tee deploy.log
  
  return ${PIPESTATUS[0]}
}

verify_deployment() {
  echo "Verifying deployment..."
  
  # Check if container exists
  if ! pct list | grep -q "$HOSTNAME"; then
    echo "✗ Container not found in pct list"
    return 1
  fi
  
  # Check if container is running
  CTID=$(pct list | grep "$HOSTNAME" | awk '{print $1}')
  STATUS=$(pct status "$CTID" | awk '{print $2}')
  
  if [ "$STATUS" != "running" ]; then
    echo "✗ Container not running (Status: $STATUS)"
    return 1
  fi
  
  # Check network connectivity
  if ! pct exec "$CTID" -- ping -c 1 1.1.1.1 &>/dev/null; then
    echo "⚠ Warning: No internet connectivity"
  fi
  
  echo "✓ Deployment verified successfully"
  echo "  Container ID: $CTID"
  echo "  Status: $STATUS"
  echo "  IP: $(pct exec "$CTID" -- hostname -I)"
  
  return 0
}

# Main deployment loop with retry
while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do
  if deploy_container; then
    if verify_deployment; then
      echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
      echo "✓ Deployment successful!"
      echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
      exit 0
    else
      echo "✗ Deployment verification failed"
    fi
  else
    echo "✗ Deployment failed"
  fi
  
  RETRY_COUNT=$((RETRY_COUNT + 1))
  
  if [ $RETRY_COUNT -lt $MAX_RETRIES ]; then
    echo "Retrying in 10 seconds..."
    sleep 10
  fi
done

echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "✗ Deployment failed after $MAX_RETRIES attempts"
echo "Check deploy.log for details"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
exit 1

Security Best Practices

Secure Deployment Script

#!/bin/bash
# secure-deploy.sh - Production-ready secure deployment

set -euo pipefail  # Exit on error, undefined vars, pipe failures

# Configuration
readonly APP="debian"
readonly HOSTNAME="secure-server"
readonly SSH_KEY_PATH="/root/.ssh/id_rsa.pub"
readonly LOG_FILE="/var/log/container-deployments.log"

# Logging function
log() {
  echo "[$(date +'%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"
}

# Validate prerequisites
validate_environment() {
  log "Validating environment..."
  
  # Check if running as root
  if [ "$EUID" -ne 0 ]; then
    log "ERROR: Must run as root"
    exit 1
  fi
  
  # Check SSH key exists
  if [ ! -f "$SSH_KEY_PATH" ]; then
    log "ERROR: SSH key not found at $SSH_KEY_PATH"
    exit 1
  fi
  
  # Check internet connectivity
  if ! curl -s --max-time 5 https://github.com &>/dev/null; then
    log "ERROR: No internet connectivity"
    exit 1
  fi
  
  log "✓ Environment validated"
}

# Secure deployment
deploy_secure() {
  log "Starting secure deployment for $HOSTNAME..."
  
  SSH_KEY=$(cat "$SSH_KEY_PATH")
  
  var_unprivileged=1 \
  var_cpu=4 \
  var_ram=4096 \
  var_disk=30 \
  var_hostname="$HOSTNAME" \
  var_brg=vmbr0 \
  var_net=dhcp \
  var_ipv6_method=disable \
  var_ssh=yes \
  var_ssh_authorized_key="$SSH_KEY" \
  var_nesting=0 \
  var_keyctl=0 \
  var_fuse=0 \
  var_protection=yes \
  var_tags=production,secure,automated \
  var_verbose=no \
    bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/${APP}.sh)" 2>&1 | tee -a "$LOG_FILE"
  
  if [ ${PIPESTATUS[0]} -eq 0 ]; then
    log "✓ Deployment successful"
    return 0
  else
    log "✗ Deployment failed"
    return 1
  fi
}

# Main execution
main() {
  validate_environment
  
  if deploy_secure; then
    log "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
    log "Secure deployment completed successfully"
    log "Container: $HOSTNAME"
    log "Features: Unprivileged, SSH-only, Protected"
    log "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
    exit 0
  else
    log "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
    log "Deployment failed - check logs at $LOG_FILE"
    log "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
    exit 1
  fi
}

main "$@"
Security Reminders:
  • Never store passwords in defaults files - use SSH keys
  • Always use unprivileged containers when possible
  • Enable container protection for production deployments
  • Use static IPs for critical infrastructure
  • Disable unnecessary features (FUSE, TUN, etc.)

Common Deployment Patterns

Docker Host

var_unprivileged=0 \
var_nesting=1 \
var_keyctl=1 \
var_cpu=4 \
var_ram=8192 \
var_disk=50 \
var_hostname=docker-host \
  bash docker-install.sh

VPN Gateway

var_tun=yes \
var_cpu=2 \
var_ram=1024 \
var_disk=10 \
var_hostname=wireguard \
var_tags="vpn;network" \
  bash wireguard-install.sh

Media Server with GPU

var_gpu=yes \
var_unprivileged=1 \
var_cpu=6 \
var_ram=8192 \
var_disk=100 \
var_hostname=plex \
var_tags="media;transcoding" \
  bash plex-install.sh

Environment Variables

Complete variable reference

Defaults System

Save reusable configurations

Troubleshooting

Common deployment issues

Build docs developers (and LLMs) love