UbuntuでIncus 7.0 LTSを使用する

LXDから派生したIncusの最新版、Incus 7.0 LTSが2026年5月5日に正式リリースされたので、Ubuntuに導入してみました。2031年6月までの5年間サポートとなっているようです。

Incusのセットアップ

sudo mkdir -p /opt/script
sudo chown $USER:$USER /opt/script
chmod u+rwx /opt/script
cd /opt/script
nano incus-setup-ubuntu2604.sh
chmod +x incus-setup-ubuntu2604.sh
sudo ./incus-setup-ubuntu2604.sh
#!/usr/bin/env bash
# =============================================================================
# Incus 7.0 LTS セットアップスクリプト (Ubuntu 26.04)
# - ストレージ・ネットワークはincus admin initで設定
# =============================================================================

set -euo pipefail

RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; CYAN='\033[0;36m'; NC='\033[0m'
info()    { echo -e "${CYAN}[INFO]${NC}  $*"; }
success() { echo -e "${GREEN}[OK]${NC}    $*"; }
warn()    { echo -e "${YELLOW}[WARN]${NC}  $*"; }
error()   { echo -e "${RED}[ERROR]${NC} $*"; exit 1; }

[[ $EUID -ne 0 ]] && error "sudoで実行してください"
REAL_USER="${SUDO_USER:-$USER}"

# =============================================================================
# 1. Zabbly リポジトリ + Incus 7.0 インストール
# =============================================================================
info "=== Zabbly リポジトリのセットアップ ==="

mkdir -p /etc/apt/keyrings
TMPKEY=$(mktemp)
curl -fsSL https://pkgs.zabbly.com/key.asc -o "$TMPKEY"

FINGERPRINT=$(gpg --show-keys --fingerprint "$TMPKEY" 2>/dev/null \
    | grep -A1 "^pub" | tail -1 | tr -d ' ')
EXPECTED="4EFC590696CB15B87C73A3AD82CC8797C838DCFD"

if [[ "$FINGERPRINT" == "$EXPECTED" ]]; then
    success "GPGキー確認済み"
    gpg --dearmor < "$TMPKEY" > /etc/apt/keyrings/zabbly.gpg
else
    rm -f "$TMPKEY"
    error "GPGキーのフィンガープリントが一致しません\n  期待値: $EXPECTED\n  実際値: $FINGERPRINT"
fi
rm -f "$TMPKEY"

CODENAME=$(. /etc/os-release && echo "${VERSION_CODENAME}")
ARCH=$(dpkg --print-architecture)

cat > /etc/apt/sources.list.d/zabbly-incus-stable.sources << EOF
Enabled: yes
Types: deb
URIs: https://pkgs.zabbly.com/incus/stable
Suites: ${CODENAME}
Components: main
Architectures: ${ARCH}
Signed-By: /etc/apt/keyrings/zabbly.gpg
EOF

apt-get update -qq
apt-get install -y incus qemu-system
apt-get install -y incus-ui-canonical

INCUS_VER=$(dpkg-query -W -f='${Version}' incus 2>/dev/null || echo "7.0")
success "Incus ${INCUS_VER} インストール完了"

# =============================================================================
# 2. ユーザーをincus-adminグループに追加
# =============================================================================
info "=== ユーザー設定 ==="

if [[ "$REAL_USER" != "root" ]]; then
    usermod -aG incus-admin "$REAL_USER"
    success "$REAL_USER を incus-admin グループに追加しました"
fi

# =============================================================================
# 3. サービス起動
# =============================================================================
info "=== Incusサービスの起動 ==="

systemctl enable --now incus
systemctl enable --now incus-startup 2>/dev/null || true

for i in {1..15}; do
    if systemctl is-active --quiet incus; then break; fi
    sleep 2
    info "起動待機中... ($i/15)"
done
systemctl is-active --quiet incus || error "Incusサービスが起動しませんでした"
success "Incusサービス起動確認"

# =============================================================================
# 完了
# =============================================================================
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo -e "${GREEN}  Incus 7.0 LTS セットアップ完了${NC}"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
if [[ "$REAL_USER" != "root" ]]; then
    echo -e "${YELLOW}  ※ グループ反映のためログアウトして再ログインしてください${NC}"
    echo "     または: newgrp incus-admin"
    echo ""
fi
echo "  次のステップ: 初期化"
echo "    sudo incus admin init"
echo ""
echo "  HTTPS有効化"
echo "    sudo incus config set core.https_address :8443"
echo ""
echo "  Web UIはTailscale経由でアクセス:"
TS_IP=$(tailscale ip -4 2>/dev/null | head -n1 || echo "<tailscale-ip>")
echo "    https://${TS_IP}:8443"
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"

いったん再起動ログアウト→再ログイン(グループ反映)

sudo reboot

再起動後に初期化。基本的に全てEnterで問題ありません。

sudo incus admin init

HTTPS有効化。もしLXD-UIを使用している環境ならおそらく8443ポートを使用していると思うので、その場合は8444番ポートなど空いている番号に。

sudo incus config set core.https_address :8443

Webブラウザでアクセス

https:// IPアドレス :8443 にアクセスします。

証明書を作成します。

読み込み時に使用するパスワードを設定。

証明書が作成されたらダウンロードし、ダウンロードしたフォルダで右クリックしてターミナルを表示。下記コマンドでインストールします。

incus config trust add-certificate incus-ui.crt

続いて、.pfxファイルをダウンロードします。使用しているブラウザによってやり方が表示されているので指示に従って作業します。

無事インストールされればアクセス出来ます。

Incusを操作するために便利なスクリプトを一括作成

LXDの時と同様ですが、ファイル名は次のようになっています。

ファイル名の変更

変更前変更後
minimal-lxd-base-create.shminimal-incus-base-create.sh
first-setup-minimal-lxd-base.shfirst-setup-minimal-incus-base.sh
docker-lxd-base-create.shdocker-incus-base-create.sh
copy-lxd-create.shcopy-incus-create.sh
snapshot-lxd.shsnapshot-incus.sh
enter-lxd-container.shenter-incus-container.sh
#!/bin/bash
set -euo pipefail

sudo mkdir -p /opt/script/incus

cat > /tmp/minimal-incus-base-create.sh << 'EOF'
#!/bin/bash
set -euo pipefail

CONTAINER="incus-base-minimal"
MOUNT_PATH="/opt/incus-data"

echo "=== incus-base-minimal コンテナを作成 ==="
incus launch images:ubuntu/26.04 "$CONTAINER"

echo "=== $MOUNT_PATH が存在しない場合は作成 ==="
if [ ! -d "$MOUNT_PATH" ]; then
    sudo mkdir -p "$MOUNT_PATH"
    if [ ! -d "$MOUNT_PATH" ]; then
        echo "エラー: $MOUNT_PATH の作成に失敗しました"
        exit 1
    fi
    echo "    $MOUNT_PATH を作成しました"
else
    echo "    $MOUNT_PATH は既に存在します"
fi

echo "=== ホストの $MOUNT_PATH をコンテナに同じパスでマウント ==="
incus config device add "$CONTAINER" opt-incus-data disk source="$MOUNT_PATH" path="$MOUNT_PATH"

echo "=== ID マッピング設定を適用 ==="
incus config set "$CONTAINER" raw.idmap "both 1000 1000"

echo "=== コンテナを再起動します ==="
incus restart "$CONTAINER"

echo "=== 完了しました ==="
EOF

cat > /tmp/first-setup-minimal-incus-base.sh << 'EOF'
#!/bin/bash
set -euo pipefail

CONTAINER="incus-base-minimal"

echo "==> コンテナ '${CONTAINER}' に接続してセットアップを開始します..."

incus exec "${CONTAINER}" -- bash -euo pipefail << 'INNER'
echo "==> apt update"
sudo apt update

echo "==> apt upgrade"
sudo apt upgrade -y

echo "==> curl インストール"
sudo apt install -y curl

echo "==> Tailscale インストール"
curl -fsSL https://tailscale.com/install.sh | sh

echo "==> セットアップ完了"
INNER

echo "==> コンテナをシャットダウンします..."
incus stop "${CONTAINER}"

echo "==> 完了"
EOF

cat > /tmp/docker-incus-base-create.sh << 'EOF'
#!/bin/bash
set -euo pipefail

SRC="incus-base-minimal"
NEW="incus-base-docker"
MOUNT_PATH="/opt/incus-data"

echo "=== コンテナをコピー: $SRC → $NEW ==="
incus copy "$SRC" "$NEW"

echo "=== コンテナを停止します(設定適用のため) ==="
incus stop "$NEW" 2>/dev/null || true

echo "=== raw.idmap を設定 ==="
incus config set "$NEW" raw.idmap "both 1000 1000"

echo "=== Nesting を Allow に設定 ==="
incus config set "$NEW" security.nesting true

echo "=== コンテナを起動 ==="
incus start "$NEW"

echo "=== マウントディレクトリの権限設定(ホスト側) ==="
sudo chown 1000:1000 "$MOUNT_PATH"
sudo chmod 775 "$MOUNT_PATH"

echo "=== 設定反映のため再起動 ==="
incus restart "$NEW"

echo "=== ネットワーク疎通を待機中 ==="
for i in $(seq 1 30); do
    if incus exec "$NEW" -- curl -fsSL --max-time 3 https://get.docker.com -o /dev/null 2>/dev/null; then
        echo "    ネットワーク疎通確認 (${i}秒)"
        break
    fi
    echo "    待機中... (${i}/30秒)"
    sleep 1
    if [ "$i" -eq 30 ]; then
        echo "エラー: ネットワークが30秒以内に疎通しませんでした" >&2
        exit 1
    fi
done

echo "=== コンテナ内でセットアップを実行 ==="
incus exec "$NEW" -- bash -euo pipefail << 'INNER'

echo "--- Docker インストール ---"
curl -fsSL https://get.docker.com | sh

echo "--- /opt/docker ディレクトリのセットアップ ---"
mkdir -p /opt/docker

if getent group docker > /dev/null 2>&1; then
    chown -R "${USER:-root}:docker" /opt/docker
    chmod -R 775 /opt/docker
    chmod -R g+s /opt/docker
    if [ "${USER:-root}" != "root" ]; then
        usermod -aG docker "$USER"
        echo "グループ変更を反映するには、newgrp docker を実行してください(再起動不要)"
    else
        echo "rootユーザーのため usermod はスキップしました"
    fi
else
    chown -R "${USER:-root}:${USER:-root}" /opt/docker
    chmod -R 755 /opt/docker
    echo "docker グループが存在しないため、オーナー権限のみ設定しました"
fi

echo "/opt/docker のセットアップ完了"
INNER

echo "=== コンテナをシャットダウン ==="
incus stop "$NEW"

echo "=== 完了: $NEW ==="
EOF

cat > /tmp/copy-incus-create.sh << 'EOF'
#!/bin/bash
set -euo pipefail

containers=($(incus list -c n --format csv))

echo "=== コピー元のコンテナを選択してください ==="
i=1
for c in "${containers[@]}"; do
    echo "$i) $c"
    ((i++))
done

read -p "番号を入力: " index

if ! [[ "$index" =~ ^[0-9]+$ ]]; then
    echo "エラー: 数字を入力してください" >&2
    exit 1
fi

index=$((index - 1))

if [ "$index" -lt 0 ] || [ "$index" -ge "${#containers[@]}" ]; then
    echo "エラー: 不正な番号です" >&2
    exit 1
fi

SRC="${containers[$index]}"
echo "選択されたコピー元: $SRC"

read -p "新しいコンテナ名を入力: " NEW

if [ -z "$NEW" ]; then
    echo "エラー: コンテナ名が空です" >&2
    exit 1
fi

echo "=== コピー開始: $SRC → $NEW ==="
incus copy "$SRC" "$NEW"

echo "=== 一旦コンテナを停止します(ID マップ適用のため) ==="
incus stop "$NEW" 2>/dev/null || true

echo "=== raw.idmap を設定します ==="
incus config set "$NEW" raw.idmap "both 1000 1000"

echo "=== コンテナを起動します ==="
incus start "$NEW"

echo "=== 設定反映のため再起動 ==="
incus restart "$NEW"

echo "=== コンテナに入ります: $NEW ==="
incus exec "$NEW" -- bash
EOF

cat > /tmp/snapshot-incus.sh << 'EOF'
#!/bin/bash
set -euo pipefail

echo "=== Incus コンテナ一覧 ==="

# コンテナ名一覧を配列に格納
containers=($(incus list -c n --format csv))

# 一覧表示
i=1
for c in "${containers[@]}"; do
    echo "$i) $c"
    ((i++))
done

# 番号選択
read -p "番号を入力: " index

# 数字チェック
if ! [[ "$index" =~ ^[0-9]+$ ]]; then
    echo "エラー: 数字を入力してください" >&2
    exit 1
fi

index=$((index - 1))

# 範囲チェック
if [ "$index" -lt 0 ] || [ "$index" -ge "${#containers[@]}" ]; then
    echo "エラー: 不正な番号です" >&2
    exit 1
fi

TARGET="${containers[$index]}"
echo "選択されたコンテナ: $TARGET"

# コメント入力
read -p "スナップショットのコメントを入力(任意・空可): " COMMENT

# スペースをハイフンに変換
COMMENT="${COMMENT// /-}"

# スナップショット名(コメントがあれば付加)
SNAP="snap-$(date +%Y%m%d-%H%M%S)"
if [ -n "$COMMENT" ]; then
    SNAP="${SNAP}-${COMMENT}"
fi

echo "=== コンテナ停止: $TARGET ==="
incus stop "$TARGET" 2>/dev/null || true

echo "=== スナップショット作成: $TARGET/$SNAP ==="
incus snapshot "$TARGET" "$SNAP"

echo "=== コンテナ起動: $TARGET ==="
incus start "$TARGET"

echo "=== 完了しました ==="
echo "作成されたスナップショット: $TARGET/$SNAP"
EOF

cat > /tmp/enter-incus-container.sh << 'EOF'
#!/bin/bash
set -euo pipefail

echo "=== Incus コンテナ一覧 ==="

containers=($(incus list -c n --format csv))

i=1
for c in "${containers[@]}"; do
    echo "$i) $c"
    ((i++))
done

read -p "番号を入力: " index

if ! [[ "$index" =~ ^[0-9]+$ ]]; then
    echo "エラー: 数字を入力してください" >&2
    exit 1
fi

index=$((index - 1))

if [ "$index" -lt 0 ] || [ "$index" -ge "${#containers[@]}" ]; then
    echo "エラー: 不正な番号です" >&2
    exit 1
fi

TARGET="${containers[$index]}"
echo "選択されたコンテナ: $TARGET"

STATUS=$(incus info "$TARGET" | awk '/Status:/ {print tolower($2)}')

if [ "$STATUS" != "running" ]; then
    echo "=== コンテナが停止中です。起動します ==="
    incus start "$TARGET"
else
    echo "=== コンテナは既に起動中です ==="
fi

echo "=== コンテナに入ります: $TARGET ==="
incus exec "$TARGET" -- bash
EOF

sudo mv /tmp/minimal-incus-base-create.sh /opt/script/incus/
sudo mv /tmp/first-setup-minimal-incus-base.sh /opt/script/incus/
sudo mv /tmp/docker-incus-base-create.sh /opt/script/incus/
sudo mv /tmp/copy-incus-create.sh /opt/script/incus/
sudo mv /tmp/snapshot-incus.sh /opt/script/incus/
sudo mv /tmp/enter-incus-container.sh /opt/script/incus/

sudo chmod +x /opt/script/incus/*.sh
sudo chown $USER:$USER /opt/script/incus/*.sh

echo "=== 完了 ==="
ls -la /opt/script/incus/

スクリプトを利用してベースコンテナを作成

クリプトを利用してベースコンテナを作成するなら次のコマンドで。

cd /opt/script/incus/

# ベースコンテナを作成
./minimal-incus-base-create.sh

# 作成したコンテナ内をアップデートしてTailscaleをインストール
./first-setup-minimal-incus-base.sh

# そのコンテナをコピーしDocker環境のコンテナを作成
./docker-incus-base-create.sh

以後は、いずれかのコンテナをコピーして利用します。

./copy-incus-create.sh

コピー後にコンテナに入りますが、一度出た環境でも、LXD-UIでターミナルに入って作業しても良いですし、下記でコンテナを選択して入れます。

./enter-incus-container.sh

コピーしたコンテナに入ったら下記で認証しておきましょう。

tailscale up

exitで一度出て、スナップショットを取っておくと安心。

./snapshot-lxd.sh
タイトルとURLをコピーしました