ファイルサーバーとしても利用可能なツールとして、「OxiCloud」や「Filebrowser」を紹介しましたが、見た目が綺麗で、ファイルやフォルダの共有も行いやすい「nextExplorer」も使いやすそうです。
実際の使用画面は次のような感じで、動作も軽快。GitHubで公開されており、セルフホストが可能です。

OnlyOfficeとの連携も出来るセットアップスクリプトを作ったので、これから導入するならこちらのほうが良いと思います。
LXDコンテナ内にDockerでセットアップ
#!/usr/bin/env bash
# =============================================================================
# nextExplorer セットアップスクリプト v3
# - /opt/nextexplorer に docker-compose.yml を配置
# - コンテナ内ポート 3000 → ホスト 127.0.0.1:3317 にバインド
# - Tailscale Serve で https://<hostname>:3317 → :3317 に追加(ポートベース)
# - 既存の Serve 設定は壊さない(--bg)
# - データディレクトリのパーミッションを正しく設定
# =============================================================================
set -euo pipefail
# ── 設定 ──────────────────────────────────────────────────────────────────────
INSTALL_DIR="/opt/nextexplorer"
HOST_PORT=3317
CONTAINER_PORT=3000
DATA_DIR="/srv/nextexplorer" # config / cache / files の実体
# コンテナ内プロセスの実行ユーザーに合わせる
# nxzai/explorer は内部で node ユーザー(uid=1000) を使用
PUID=$(id -u)
PGID=$(id -g)
# ── 色付きログ ─────────────────────────────────────────────────────────────────
info() { echo -e "\033[1;34m[INFO]\033[0m $*"; }
ok() { echo -e "\033[1;32m[ OK ]\033[0m $*"; }
warn() { echo -e "\033[1;33m[WARN]\033[0m $*"; }
die() { echo -e "\033[1;31m[ERR ]\033[0m $*" >&2; exit 1; }
# ── 前提チェック ───────────────────────────────────────────────────────────────
info "前提確認..."
command -v docker >/dev/null 2>&1 || die "docker が見つかりません"
docker compose version >/dev/null 2>&1 || die "docker compose (v2) が見つかりません"
command -v tailscale >/dev/null 2>&1 || die "tailscale が見つかりません"
tailscale status >/dev/null 2>&1 || die "Tailscale が認証されていません"
ok "前提 OK"
# ── Tailscale hostname 取得 ────────────────────────────────────────────────────
TS_HOSTNAME=$(tailscale status --json \
| python3 -c "import sys,json; d=json.load(sys.stdin); print(d['Self']['DNSName'].rstrip('.'))" \
2>/dev/null) || die "Tailscale の DNSName を取得できませんでした"
PUBLIC_URL="https://${TS_HOSTNAME}:${HOST_PORT}"
info "Tailscale ホスト : ${TS_HOSTNAME}"
info "PUBLIC_URL : ${PUBLIC_URL}"
# ── ディレクトリ作成 & パーミッション設定 ─────────────────────────────────────
info "ディレクトリ作成..."
mkdir -p "${INSTALL_DIR}"
mkdir -p "${DATA_DIR}/config"
mkdir -p "${DATA_DIR}/cache"
mkdir -p "${DATA_DIR}/files"
# コンテナ内 node(1000) / コンテナ外の実行ユーザー 両方が書けるように
# ホスト uid と合わせるか、777 で開放するかを選択
# → ここでは実行ユーザー所有 + グループ書き込み可 にする
chown -R "${PUID}:${PGID}" "${DATA_DIR}"
chmod -R u=rwX,g=rwX,o=rX "${DATA_DIR}"
# コンテナが uid=1000 で動く場合に備えて ACL を追加(acl パッケージがある場合のみ)
if command -v setfacl >/dev/null 2>&1; then
setfacl -R -m u:1000:rwX "${DATA_DIR}"
setfacl -R -d -m u:1000:rwX "${DATA_DIR}"
info "ACL: uid=1000 に rwX を付与しました"
else
# ACL が使えない場合は o+w を付与してコンテナ書き込みを許可
chmod -R o+w "${DATA_DIR}/config" "${DATA_DIR}/cache" "${DATA_DIR}/files"
warn "acl パッケージが未インストールのため chmod o+w で代替しました"
warn " sudo apt install acl で改善できます"
fi
ok "ディレクトリ & パーミッション設定完了"
# ── SESSION_SECRET 生成(既存ファイルがあれば再利用) ─────────────────────────
SECRET_FILE="${INSTALL_DIR}/.session_secret"
if [[ -f "${SECRET_FILE}" ]]; then
SESSION_SECRET=$(cat "${SECRET_FILE}")
info "既存の SESSION_SECRET を再利用します"
else
SESSION_SECRET=$(python3 -c "import secrets; print(secrets.token_hex(32))")
echo "${SESSION_SECRET}" > "${SECRET_FILE}"
chmod 600 "${SECRET_FILE}"
info "SESSION_SECRET を新規生成しました"
fi
# ── docker-compose.yml 生成 ───────────────────────────────────────────────────
info "docker-compose.yml を生成..."
cat > "${INSTALL_DIR}/docker-compose.yml" <<EOF
# nextExplorer — docker-compose.yml
# 生成日: $(date '+%Y-%m-%d %H:%M:%S')
# アクセス URL: ${PUBLIC_URL}
services:
nextexplorer:
image: nxzai/explorer:latest
container_name: nextexplorer
restart: unless-stopped
ports:
# ループバックのみ。Tailscale Serve 経由でのみアクセス可能
- "127.0.0.1:${HOST_PORT}:${CONTAINER_PORT}"
environment:
NODE_ENV: production
PORT: "${CONTAINER_PORT}"
# ポートベースの URL(サブパスなし)
PUBLIC_URL: "${PUBLIC_URL}"
# Tailscale Serve (loopback) を信頼
TRUST_PROXY: "loopback"
# 再起動をまたいでセッションを維持
SESSION_SECRET: "${SESSION_SECRET}"
# コンテナ内プロセスの UID/GID をホストに合わせる → 書き込み権限の解決
PUID: "${PUID}"
PGID: "${PGID}"
volumes:
- ${DATA_DIR}/config:/config
- ${DATA_DIR}/cache:/cache
# /mnt/<ラベル名> が UI 上のボリュームになる。必要に応じて追加
- ${DATA_DIR}/files:/mnt/Files
EOF
ok "docker-compose.yml → ${INSTALL_DIR}/docker-compose.yml"
# ── コンテナ起動 ──────────────────────────────────────────────────────────────
info "コンテナを起動..."
docker compose -f "${INSTALL_DIR}/docker-compose.yml" pull
docker compose -f "${INSTALL_DIR}/docker-compose.yml" up -d
ok "コンテナ起動完了"
# ── Tailscale Serve 設定(ポートベース) ──────────────────────────────────────
info "既存の Tailscale Serve 設定を確認..."
EXISTING_SERVE=$(tailscale serve status 2>/dev/null || true)
if echo "${EXISTING_SERVE}" | grep -q ":${HOST_PORT}"; then
warn "ポート ${HOST_PORT} はすでに Tailscale Serve に登録されています。スキップします。"
else
info "Tailscale Serve にポート ${HOST_PORT} の HTTPS プロキシを追加..."
# --bg : バックグラウンドモード(既存設定を保持したまま追加)
# --https=N : Tailnet 内で HTTPS ポート N として公開
tailscale serve --bg --https="${HOST_PORT}" "http://127.0.0.1:${HOST_PORT}"
ok "Tailscale Serve 設定追加完了"
fi
# ── 完了サマリー ──────────────────────────────────────────────────────────────
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
ok "セットアップ完了!"
echo ""
echo " アクセス URL : ${PUBLIC_URL}"
echo " 設定ファイル : ${INSTALL_DIR}/docker-compose.yml"
echo " データ領域 : ${DATA_DIR}/"
echo " ├─ config/ (DB・設定)"
echo " ├─ cache/ (サムネイルキャッシュ)"
echo " └─ files/ (公開ファイル置き場 → UI上: Files)"
echo ""
echo " ボリュームを追加したい場合:"
echo " ${INSTALL_DIR}/docker-compose.yml の volumes に"
echo " - /your/path:/mnt/LabelName を追記して"
echo " docker compose -f ${INSTALL_DIR}/docker-compose.yml up -d"
echo ""
echo " 現在の Tailscale Serve 設定:"
tailscale serve status
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
インストールが完了してアクセスすれば次のような画面が表示されるので、メールアドレスやパスワードを設定してログインします。

Locationsにフォルダを追加する方法
docker-compose.yml の volumes セクションにマウントを追加するだけです。
volumes: - /srv/nextexplorer/config:/config - /srv/nextexplorer/cache:/cache - /srv/nextexplorer/files:/mnt/Files # 既存 - /home/user/photos:/mnt/Photos # 追加例 - /mnt/nas/documents:/mnt/Documents # 追加例
/mnt/ 以降の名前(Photos、Documents など)がそのまま UI 上のラベルになります。
編集と反映するコマンド
# 編集
nano /opt/nextexplorer/docker-compose.yml
# 編集後
docker compose -f /opt/nextexplorer/docker-compose.yml up -d
デフォルトの Files フォルダを別パスに変えたい場合も同じで、既存の行のホスト側パスを書き換えるだけです。
# 変更前
- /srv/nextexplorer/files:/mnt/Files
# 変更後(例:/data/storage を Files として公開)
- /opt/lxd-data:/mnt/Files
注意点として、追加したディレクトリにコンテナ(uid=1000)が書き込む必要がある場合は、スクリプトで設定したのと同じようにパーミッションを合わせてください。
# acl が入っている場合
sudo setfacl -R -m u:1000:rwX /your/path
sudo setfacl -R -d -m u:1000:rwX /your/path
# acl なしの場合
sudo chmod -R o+w /your/path
インストール時にマウントフォルダを登録
#!/usr/bin/env bash
# =============================================================================
# nextExplorer セットアップスクリプト v3
# - /opt/nextexplorer に docker-compose.yml を配置
# - コンテナ内ポート 3000 → ホスト 127.0.0.1:3317 にバインド
# - Tailscale Serve で https://<hostname>:3317 → :3317 に追加(ポートベース)
# - 既存の Serve 設定は壊さない(--bg)
# - データディレクトリのパーミッションを正しく設定
# =============================================================================
set -euo pipefail
# ── 固定設定 ──────────────────────────────────────────────────────────────────
INSTALL_DIR="/opt/nextexplorer"
HOST_PORT=3317
CONTAINER_PORT=3000
DATA_DIR="/srv/nextexplorer" # config / cache の実体(マウントとは別)
DEFAULT_MOUNT_SRC="/opt/lxd-data"
DEFAULT_MOUNT_LABEL="Files"
# コンテナ内プロセスの実行ユーザーに合わせる
# nxzai/explorer は内部で node ユーザー(uid=1000) を使用
PUID=$(id -u)
PGID=$(id -g)
# ── 色付きログ ─────────────────────────────────────────────────────────────────
info() { echo -e "\033[1;34m[INFO]\033[0m $*"; }
ok() { echo -e "\033[1;32m[ OK ]\033[0m $*"; }
warn() { echo -e "\033[1;33m[WARN]\033[0m $*"; }
die() { echo -e "\033[1;31m[ERR ]\033[0m $*" >&2; exit 1; }
# ── 前提チェック ───────────────────────────────────────────────────────────────
info "前提確認..."
command -v docker >/dev/null 2>&1 || die "docker が見つかりません"
docker compose version >/dev/null 2>&1 || die "docker compose (v2) が見つかりません"
command -v tailscale >/dev/null 2>&1 || die "tailscale が見つかりません"
tailscale status >/dev/null 2>&1 || die "Tailscale が認証されていません"
ok "前提 OK"
# ── Tailscale hostname 取得 ────────────────────────────────────────────────────
TS_HOSTNAME=$(tailscale status --json \
| python3 -c "import sys,json; d=json.load(sys.stdin); print(d['Self']['DNSName'].rstrip('.'))" \
2>/dev/null) || die "Tailscale の DNSName を取得できませんでした"
PUBLIC_URL="https://${TS_HOSTNAME}:${HOST_PORT}"
info "Tailscale ホスト : ${TS_HOSTNAME}"
info "PUBLIC_URL : ${PUBLIC_URL}"
# ── マウントディレクトリの入力 ────────────────────────────────────────────────
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " マウントするディレクトリを設定します。"
echo " 複数指定可能。空 Enter で終了(1つも入力しない場合はデフォルト設定を使用)。"
echo " デフォルト: ${DEFAULT_MOUNT_SRC} → UI ラベル「${DEFAULT_MOUNT_LABEL}」"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
# MOUNT_ENTRIES は "ホストパス:ラベル名" の配列
declare -a MOUNT_ENTRIES=()
mount_index=1
while true; do
read -rp " マウント #${mount_index} ホストパス(空 Enter でスキップ/終了): " input_path
# 空入力 → ループ終了
[[ -z "${input_path}" ]] && break
# パスの存在確認
if [[ ! -d "${input_path}" ]]; then
warn "${input_path} は存在しません。作成しますか? [y/N]: "
read -rp " " yn
if [[ "${yn}" =~ ^[Yy]$ ]]; then
mkdir -p "${input_path}"
ok "${input_path} を作成しました"
else
warn "スキップします"
continue
fi
fi
# UI ラベル入力(未入力ならディレクトリ名をそのまま使用)
default_label=$(basename "${input_path}")
read -rp " マウント #${mount_index} UI ラベル名(空 Enter で「${default_label}」): " input_label
[[ -z "${input_label}" ]] && input_label="${default_label}"
MOUNT_ENTRIES+=("${input_path}:${input_label}")
ok "追加: ${input_path} → /mnt/${input_label}"
(( mount_index++ ))
done
# 1つも入力されなかった場合はデフォルト設定を使用
if [[ ${#MOUNT_ENTRIES[@]} -eq 0 ]]; then
warn "入力なし。デフォルト設定を使用します: ${DEFAULT_MOUNT_SRC} → ${DEFAULT_MOUNT_LABEL}"
mkdir -p "${DEFAULT_MOUNT_SRC}"
MOUNT_ENTRIES=("${DEFAULT_MOUNT_SRC}:${DEFAULT_MOUNT_LABEL}")
fi
echo ""
info "マウント設定:"
for entry in "${MOUNT_ENTRIES[@]}"; do
src="${entry%%:*}"
label="${entry##*:}"
echo " ${src} → /mnt/${label}"
done
echo ""
# ── ディレクトリ作成 & パーミッション設定 ─────────────────────────────────────
info "ディレクトリ作成..."
mkdir -p "${INSTALL_DIR}"
mkdir -p "${DATA_DIR}/config"
mkdir -p "${DATA_DIR}/cache"
apply_permissions() {
local target="$1"
chown -R "${PUID}:${PGID}" "${target}"
chmod -R u=rwX,g=rwX,o=rX "${target}"
if command -v setfacl >/dev/null 2>&1; then
setfacl -R -m u:1000:rwX "${target}"
setfacl -R -d -m u:1000:rwX "${target}"
else
chmod -R o+w "${target}"
fi
}
# DATA_DIR(config/cache)にパーミッション適用
chown -R "${PUID}:${PGID}" "${DATA_DIR}"
chmod -R u=rwX,g=rwX,o=rX "${DATA_DIR}"
# 各マウントソースにもパーミッション適用
for entry in "${MOUNT_ENTRIES[@]}"; do
src="${entry%%:*}"
apply_permissions "${src}"
done
if command -v setfacl >/dev/null 2>&1; then
setfacl -R -m u:1000:rwX "${DATA_DIR}"
setfacl -R -d -m u:1000:rwX "${DATA_DIR}"
info "ACL: uid=1000 に rwX を付与しました"
else
chmod -R o+w "${DATA_DIR}/config" "${DATA_DIR}/cache"
warn "acl パッケージが未インストールのため chmod o+w で代替しました"
warn " sudo apt install acl で改善できます"
fi
ok "ディレクトリ & パーミッション設定完了"
# ── SESSION_SECRET 生成(既存ファイルがあれば再利用) ─────────────────────────
SECRET_FILE="${INSTALL_DIR}/.session_secret"
if [[ -f "${SECRET_FILE}" ]]; then
SESSION_SECRET=$(cat "${SECRET_FILE}")
info "既存の SESSION_SECRET を再利用します"
else
SESSION_SECRET=$(python3 -c "import secrets; print(secrets.token_hex(32))")
echo "${SESSION_SECRET}" > "${SECRET_FILE}"
chmod 600 "${SECRET_FILE}"
info "SESSION_SECRET を新規生成しました"
fi
# ── docker-compose.yml 生成 ───────────────────────────────────────────────────
info "docker-compose.yml を生成..."
# volumes セクションを動的に組み立て
VOLUMES_YAML=" - ${DATA_DIR}/config:/config"$'\n'
VOLUMES_YAML+=" - ${DATA_DIR}/cache:/cache"
for entry in "${MOUNT_ENTRIES[@]}"; do
src="${entry%%:*}"
label="${entry##*:}"
VOLUMES_YAML+=$'\n'" - ${src}:/mnt/${label}"
done
cat > "${INSTALL_DIR}/docker-compose.yml" <<EOF
# nextExplorer — docker-compose.yml
# 生成日: $(date '+%Y-%m-%d %H:%M:%S')
# アクセス URL: ${PUBLIC_URL}
services:
nextexplorer:
image: nxzai/explorer:latest
container_name: nextexplorer
restart: unless-stopped
ports:
# ループバックのみ。Tailscale Serve 経由でのみアクセス可能
- "127.0.0.1:${HOST_PORT}:${CONTAINER_PORT}"
environment:
NODE_ENV: production
PORT: "${CONTAINER_PORT}"
PUBLIC_URL: "${PUBLIC_URL}"
TRUST_PROXY: "loopback"
SESSION_SECRET: "${SESSION_SECRET}"
PUID: "${PUID}"
PGID: "${PGID}"
volumes:
${VOLUMES_YAML}
EOF
ok "docker-compose.yml → ${INSTALL_DIR}/docker-compose.yml"
# ── コンテナ起動 ──────────────────────────────────────────────────────────────
info "コンテナを起動..."
docker compose -f "${INSTALL_DIR}/docker-compose.yml" pull
docker compose -f "${INSTALL_DIR}/docker-compose.yml" up -d
ok "コンテナ起動完了"
# ── Tailscale Serve 設定(ポートベース) ──────────────────────────────────────
info "既存の Tailscale Serve 設定を確認..."
EXISTING_SERVE=$(tailscale serve status 2>/dev/null || true)
if echo "${EXISTING_SERVE}" | grep -q ":${HOST_PORT}"; then
warn "ポート ${HOST_PORT} はすでに Tailscale Serve に登録されています。スキップします。"
else
info "Tailscale Serve にポート ${HOST_PORT} の HTTPS プロキシを追加..."
# --bg : バックグラウンドモード(既存設定を保持したまま追加)
# --https=N : Tailnet 内で HTTPS ポート N として公開
tailscale serve --bg --https="${HOST_PORT}" "http://127.0.0.1:${HOST_PORT}"
ok "Tailscale Serve 設定追加完了"
fi
# ── 完了サマリー ──────────────────────────────────────────────────────────────
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
ok "セットアップ完了!"
echo ""
echo " アクセス URL : ${PUBLIC_URL}"
echo " 設定ファイル : ${INSTALL_DIR}/docker-compose.yml"
echo " データ領域 : ${DATA_DIR}/"
echo " ├─ config/ (DB・設定)"
echo " └─ cache/ (サムネイルキャッシュ)"
echo ""
echo " マウント済みボリューム:"
for entry in "${MOUNT_ENTRIES[@]}"; do
src="${entry%%:*}"
label="${entry##*:}"
echo " ${src} → UI ラベル「${label}」"
done
echo ""
echo " ボリュームを追加したい場合:"
echo " ${INSTALL_DIR}/docker-compose.yml の volumes に"
echo " - /your/path:/mnt/LabelName を追記して"
echo " docker compose -f ${INSTALL_DIR}/docker-compose.yml up -d"
echo ""
echo " 現在の Tailscale Serve 設定:"
tailscale serve status
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"


