JPiereをDockerComposeでインストールしてみた【日本企業向けオープンソースERP】

オープンソースERPの「iDempiere」を日本の商習慣に対応させた「JPiere」を、DockerComposeで構築してみました。企業級ERPシステムのDocker化は想像以上に複雑で、特にJetty設定の動的生成やデータベース初期化の自動化などめちゃムズかったです

https://github.com/yonaka15/jpiere-docker-compose

## JPiereとは何か

JPiereは、世界的に利用されているオープンソースERP「iDempiere」を日本の商習慣に適応させたERPシステムです。消費税計算、日本の会計基準、商取引慣行などに対応しており、中小企業から大企業まで幅広く利用されているそうです。

### 主な特徴

- 販売管理、購買管理、在庫管理、財務会計の統合システム
- 日本の消費税法に完全対応
- Webベースのユーザーインターフェース
- 多言語対応（日本語、英語など）
- プラグインによる機能拡張

## 今回構築する環境

### システム構成

**アプリケーションサーバー**
- JPiere 11（iDempiere 11ベース）
- Java 17（Eclipse Temurin）
- Jetty Webサーバー

**データベース**
- PostgreSQL 13
- 日本語対応設定（UTF-8、Locale C）


## プロジェクト構成

実際のファイル構成は以下の通りです。

```
jpieredocker/
├── Dockerfile                               # JPiereアプリケーション用イメージ
├── docker-compose.yaml                      # サービス構成定義
├── docker-entrypoint.sh                     # 起動時スクリプト
└── files/
    ├── JPiereServer11.gtk.linux.x86_64.zip  # JPiereサーバー本体
    └── ExpDat.jar                           # データベース初期データ
```

`files/`ディレクトリにダウンロードしたJPiereファイルを配置しました。

## Docker Composeの詳細設定

本当は環境変数を使うべきでしょうが簡易的に…

### PostgreSQLサービス

```yaml
postgres:
  image: postgres:13
  platform: linux/amd64
  hostname: "postgres"
  environment:
    - POSTGRES_DB=postgres
    - POSTGRES_USER=postgres
    - POSTGRES_PASSWORD=postgres
    - POSTGRES_INITDB_ARGS=--encoding=UTF-8 --lc-collate=C --lc-ctype=C
  volumes:
    - postgres_data:/var/lib/postgresql/data
    - ./init-postgres.sql:/docker-entrypoint-initdb.d/init-postgres.sql
  ports:
    - "5432:5432"
  networks:
    - jpiere_network
  healthcheck:
    test: ["CMD-SHELL", "pg_isready -U postgres"]
    interval: 10s
    timeout: 5s
    retries: 5
```

**重要なポイント：**

### JPiereサービス

```yaml
jpiere:
  build:
    context: .
    dockerfile: Dockerfile
  platform: linux/amd64
  image: jpiere:11
  hostname: "jpiere"
  ports:
    - "8080:8080"    # Web UI
    - "12612:12612"  # Telnet管理コンソール
  environment:
    - TZ=Asia/Tokyo
    - DB_NAME=idempiere
    - DB_HOST=postgres
    - DB_PORT=5432
    - DB_USER=adempiere
    - DB_PASS=adempiere
    - DB_ADMIN_PASS=postgres
    - JAVA_OPTS=-Xms256M -Xmx2048M
    - MIGRATE_EXISTING_DATABASE=false
    - DISABLE_SSL=true
  volumes:
    - jpiere_config:/opt/idempiere/configuration
    - jpiere_plugins:/opt/idempiere/plugins
    - jpiere_log:/opt/idempiere/log
  depends_on:
    postgres:
      condition: service_healthy
  networks:
    - jpiere_network
```

**重要なポイント：**

**SSL無効化**: 開発環境での簡単な接続のため

## Dockerfileの詳細解析

### 基盤イメージとパッケージ

```dockerfile
FROM --platform=linux/amd64 eclipse-temurin:17-jdk-jammy

ENV IDEMPIERE_VERSION 11
ENV IDEMPIERE_HOME /opt/idempiere
ENV IDEMPIERE_PLUGINS_HOME $IDEMPIERE_HOME/plugins
ENV IDEMPIERE_LOGS_HOME $IDEMPIERE_HOME/log

# 必要なパッケージのインストール
RUN apt-get update && \
  apt-get install -y --no-install-recommends nano postgresql-client unzip && \
  rm -rf /var/lib/apt/lists/*
```

Eclipse Temurin 17を使用することで、Oracle JDKのライセンス問題を回避しています。

### JPiereサーバーの展開処理

```dockerfile
# ローカルファイルをコンテナにコピー
COPY files/JPiereServer11.gtk.linux.x86_64.zip /tmp/jpiere-server.zip
COPY files/ExpDat.jar /tmp/ExpDat.jar

# JPiereサーバーのセットアップ
RUN echo "=== JPiere Server Setup Start ===" && \
  echo "Hash: $(md5sum /tmp/jpiere-server.zip)" > $IDEMPIERE_HOME/MD5SUMS && \
  echo "Date: $(date)" >> $IDEMPIERE_HOME/MD5SUMS && \
  echo "=== Extracting JPiere Server ===" && \
  cd /tmp && \
  unzip -q jpiere-server.zip && \
  echo "=== Finding jpiere-server directory ===" && \
  JPIERE_DIR=$(find /tmp -maxdepth 1 -name "jpiere-server*" -type d | head -1) && \
  echo "Found JPiere directory: $JPIERE_DIR" && \
  if [ -n "$JPIERE_DIR" ] && [ -d "$JPIERE_DIR" ]; then \
    cp -r "$JPIERE_DIR"/* "$IDEMPIERE_HOME"/ && \
    rm -rf "$JPIERE_DIR" && \
    rm -f /tmp/jpiere-server.zip; \
  else \
    echo "ERROR: JPiere server directory not found!" && \
    exit 1; \
  fi
```

この部分で重要なのは：

1. **MD5ハッシュ記録**: ビルド時のファイル整合性を記録
2. **動的ディレクトリ検索**: ZIPファイル内のディレクトリ名を自動検出
3. **エラーハンドリング**: 展開に失敗した場合はビルド停止

### データベース初期データの処理

```dockerfile
# ExpDat.jarの解凍（JPiere用PostgreSQLダンプファイル）
RUN echo "=== ExpDat.jar Processing Start ===" && \
  cd /tmp && \
  if [ -f ExpDat.jar ]; then \
    unzip ExpDat.jar && \
    ls -la ExpDat.dmp && \
    echo "ExpDat.dmpファイルを/tmp/に配置完了" && \
    rm ExpDat.jar; \
  else \
    echo "ERROR: ExpDat.jar not found!" && \
    exit 1; \
  fi
```

ExpDat.jarには、JPiere用のPostgreSQLダンプファイル（ExpDat.dmp）が含まれています。これがJPiereのマスターデータと初期設定データです。

### 環境設定ファイルの生成

```dockerfile
# myEnvironment.shの作成と必要ファイルの確認
RUN echo "=== Environment Setup ===" && \
  if [ ! -d "$IDEMPIERE_HOME/utils" ]; then \
    mkdir -p "$IDEMPIERE_HOME/utils"; \
  fi && \
  if [ ! -f "$IDEMPIERE_HOME/utils/myEnvironment.sh" ]; then \
    echo "#!/bin/bash" > "$IDEMPIERE_HOME/utils/myEnvironment.sh" && \
    echo "export JAVA_HOME=${JAVA_HOME}" >> "$IDEMPIERE_HOME/utils/myEnvironment.sh" && \
    echo "export IDEMPIERE_HOME=${IDEMPIERE_HOME}" >> "$IDEMPIERE_HOME/utils/myEnvironment.sh" && \
    echo "export PATH=\$PATH:\$JAVA_HOME/bin" >> "$IDEMPIERE_HOME/utils/myEnvironment.sh" && \
    chmod +x "$IDEMPIERE_HOME/utils/myEnvironment.sh"; \
  fi
```

iDempiereが期待する環境設定ファイルを動的に生成しています。

## エントリーポイントスクリプトの複雑さ

docker-entrypoint.shは、このプロジェクトで最も複雑で重要な部分です。**この部分の実装に最も時間がかかりました。**

### 環境変数の設定

```bash
# 環境変数設定
JAVA_OPTIONS=${JAVA_OPTIONS:-""}
KEY_STORE_PASS=${KEY_STORE_PASS:-myPassword}
KEY_STORE_ON=${KEY_STORE_ON:-idempiere.org}
KEY_STORE_OU=${KEY_STORE_OU:-"JPiere Docker"}
HOST=${HOST:-0.0.0.0}
IDEMPIERE_PORT=${IDEMPIERE_PORT:-8080}
IDEMPIERE_SSL_PORT=${IDEMPIERE_SSL_PORT:-8443}
TELNET_PORT=${TELNET_PORT:-12612}
DB_HOST=${DB_HOST:-postgres}
DB_PORT=${DB_PORT:-5432}
DB_NAME=${DB_NAME:-idempiere}
DB_USER=${DB_USER:-adempiere}
DB_PASS=${DB_PASS:-adempiere}
DB_ADMIN_PASS=${DB_ADMIN_PASS:-postgres}
MIGRATE_EXISTING_DATABASE=${MIGRATE_EXISTING_DATABASE:-false}
DISABLE_SSL=${DISABLE_SSL:-false}
```

これらの環境変数は、iDempiereの`console-setup.sh`に自動入力するために使用されます。

### PostgreSQL接続待機

Postgresとの接続に何度も失敗したので接続確認するようにしました

```bash
# PostgreSQL接続待機
RETRIES=30
until PGPASSWORD=$DB_ADMIN_PASS psql -h $DB_HOST -U postgres -c "\q" > /dev/null 2>&1 || [[ $RETRIES == 0 ]]; do
    echo "Waiting for postgres server, $((RETRIES--)) remaining attempts..."
    sleep 1
done

if [[ $RETRIES == 0 ]]; then
    echo "PostgreSQL connection failed. Shutting down..."
    exit 1
fi
```

### Jetty設定の動的生成（最難関部分）

Jettyサーバー設定ファイルの動的生成もめちゃくちゃややこしかったです。

```bash
setup_jetty_config() {
    echo "=== Setting up Jetty configuration files ==="
    
    local jetty_etc_dir="$IDEMPIERE_HOME/jettyhome/etc"
    local template_dir="$IDEMPIERE_HOME/org.adempiere.server-feature/jettyhome/etc"
    
    # テンプレートディレクトリが存在しない場合は、現在のディレクトリをテンプレートとして使用
    if [[ ! -d "$template_dir" ]]; then
        template_dir="$jetty_etc_dir"
    fi
    
    # テンプレートファイルの処理
    for template_file in "$template_dir"/*-template.xml; do
        if [[ -f "$template_file" ]]; then
            local basename=$(basename "$template_file")
            local target_file="$jetty_etc_dir/${basename/-template/}"
            
            # プレースホルダーの置換
            sed -e "s|@ADEMPIERE_SSL_PORT@|$IDEMPIERE_SSL_PORT|g" \
                -e "s|@ADEMPIERE_PORT@|$IDEMPIERE_PORT|g" \
                -e "s|@ADEMPIERE_WEB_PORT@|$IDEMPIERE_PORT|g" \
                -e "s|@ADEMPIERE_WEB_SSL_PORT@|$IDEMPIERE_SSL_PORT|g" \
                -e "s|@ADEMPIERE_HOST@|$HOST|g" \
                -e "s|@ADEMPIERE_KEYSTORE@|$jetty_etc_dir/keystore|g" \
                -e "s|@ADEMPIERE_KEYSTOREPASS@|$KEY_STORE_PASS|g" \
                -e "s|@ADEMPIERE_KEYPASS@|$KEY_STORE_PASS|g" \
                "$template_file" > "$target_file"
        fi
    done
}
```

1. **テンプレートファイルの場所**: JPiereのディレクトリ構造が複雑で、テンプレートファイルの場所を特定するのに時間がかかりました
2. **プレースホルダー置換**: 一つでも置換し忘れるとJettyが起動しません
3. **SSL設定の処理**: 最近はゲートウェイを使うのが普通だと思う。SSLを無効化したい

### SSL無効化の処理

```bash
# SSL設定の無効化（オプション）
if [[ "${DISABLE_SSL:-false}" == "true" ]]; then
    echo "=== Disabling SSL configuration (DISABLE_SSL=true) ==="
    
    # SSL設定ファイルを削除するのではなく、最小限の設定ファイルを作成
    if [[ -f "$jetty_etc_dir/jetty-ssl.xml" ]]; then
        cat > "$jetty_etc_dir/jetty-ssl.xml" << 'EOF'
<?xml version="1.0"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://www.eclipse.org/jetty/configure_10_0.dtd">
<!-- SSL disabled configuration -->
<Configure id="sslContextFactory" class="org.eclipse.jetty.util.ssl.SslContextFactory$Server">
  <Set name="Provider"><Property name="jetty.sslContext.provider"/></Set>
</Configure>
EOF
    fi
fi
```

SSL設定ファイルを単純に削除するとJettyが起動しないため、最小限の内容で上書きする必要がありました。

### console-setup.shの自動実行

```bash
# コンソールセットアップ実行
echo "Executing console-setup..."
echo -e "$JAVA_HOME\n$JAVA_OPTIONS\n$IDEMPIERE_HOME\n$KEY_STORE_PASS\n$KEY_STORE_ON\n$KEY_STORE_OU\n$KEY_STORE_O\n$KEY_STORE_L\n$KEY_STORE_S\n$KEY_STORE_C\n$HOST\n$IDEMPIERE_PORT\n$IDEMPIERE_SSL_PORT\nN\n2\n$DB_HOST\n$DB_PORT\n$DB_NAME\n$DB_USER\n$DB_PASS\n$DB_ADMIN_PASS\n$MAIL_HOST\n$MAIL_USER\n$MAIL_PASS\n$MAIL_ADMIN\nY\n" | ./console-setup.sh
```

iDempiereの対話式セットアップを自動化するため、改行区切りで全ての入力値を事前に用意して`echo -e`でパイプしています。

### データベース初期化の自動判定

```bash
# データベース初期化チェック
if ! PGPASSWORD=$DB_PASS psql -h $DB_HOST -U $DB_USER -d $DB_NAME -c "\q" > /dev/null 2>&1 ; then
    echo "Database '$DB_NAME' not found or user '$DB_USER' doesn't exist, initializing..."
    
    # PostgreSQLユーザー作成
    PGPASSWORD=$DB_ADMIN_PASS psql -h $DB_HOST -U postgres -c "CREATE USER $DB_USER WITH PASSWORD '$DB_PASS';" 2>/dev/null
    
    # データベース作成
    PGPASSWORD=$DB_ADMIN_PASS psql -h $DB_HOST -U postgres -c "CREATE DATABASE $DB_NAME OWNER $DB_USER;" 2>/dev/null
    
    # JPiere固有: ExpDat.dmpでデータベース初期化
    echo "Importing JPiere database from ExpDat.dmp..."
    PGPASSWORD=$DB_ADMIN_PASS psql -h $DB_HOST -U postgres -d $DB_NAME -f /tmp/ExpDat.dmp
    
    # データベース同期
    cd utils
    ./RUN_SyncDB.sh
    cd ..
    
    # データベース署名
    ./sign-database-build.sh
fi
```

この部分では、データベースとユーザーの存在確認、作成、ExpDat.dmpからの初期化、スキーマ同期、署名という一連の処理を自動化しています。

## まとめ

JPiereのDockerCompose環境構築を通じて学んだことを率直に共有します。

### 苦労した点

**1. 企業級ERPの複雑さ**

単純なWebアプリケーションとは次元の違う複雑さでした。特に：

- Jetty設定ファイルの動的生成
- データベース初期化の自動化
- SSL設定の適切な無効化
- iDempiereのconsole-setup.shの自動化

これらの実装に、予想以上の時間がかかりました。

**2. ドキュメントの不足**

JPiereやiDempiereのDocker化に関する情報は非常に限られており、エラーメッセージもJavaスタックトレース中心で根本原因の特定が困難でした。

**3. Apple Siliconでの性能問題**

DockerでRosettaを有効にしてx86_64エミュレーションしています。


## あとがき

本当にこんなにインストール大変なんでしょうか

正しい方法をご存知でしたらご教示ください