Article

無料で作る社員研修のeラーニング

高田晃太郎
無料で作る社員研修のeラーニング

Brandon Hall Groupの調査では、eラーニングは対面研修に比べ学習所要時間を40〜60%短縮できる傾向があると報告されています[1]。一方で、導入の初期費用が壁になることも多い。ここで鍵になるのが、世界的に広く使われているオープンソースのLMS(学習管理システム)Moodle[2]と、H5P(インタラクティブ教材の著作ツール)、SCORM/xAPI(学習データの標準規格)[3][4]の組み合わせです。これらを活用すれば、ライセンス費0円で「社員研修のeラーニング」を現実的に立ち上げられます。CTOの設計観点では、無料ツールの寄せ集めではなく、セキュリティ(安全な通信・権限管理)、スケーラビリティ(負荷対応)、学習効果測定(ログと分析)の実務要件を満たすアーキテクチャに整理することが本質です。クラウドの無料枠や手持ちサーバーを使えば初期費用0円でのスタートも十分に狙えます。

無料で実現するための技術選定と全体像

無料で社員研修のeラーニングを成立させるには、ライセンス費ゼロのコンポーネントを要件に合わせて無理なく組み合わせます。中核は「LMS(Moodle)」「著作と配信(H5P/SCORM)」「トラッキング(xAPI/LRS)」「認証・配信(TLS/Nginx/Docker)」の四層で考えると整理しやすい。LMSにはMoodleを置くと、ユーザー管理、コース編成、成績管理、クイズ、SCORM取り込みまで標準機能で賄えます[2]。教材の著作にはH5P(ブラウザで作れるインタラクティブ教材ツール)を使うと、非エンジニアでも更新しやすく、Moodleとの親和性も高い[3][4]。トラッキングはまずMoodleの標準ログとSCORM 1.2で十分ですが、学習行動を細かく見たい場合はxAPI(学習体験の記録規格)とオープンソースLRS(Learning Record Store:学習履歴の保管先)を組み合わせます。認証や配信面は、Let’s EncryptによるTLS(通信の暗号化)[5]、Nginx(リバースプロキシ/HTTPサーバ)、Docker(コンテナ化)を選べば、無料かつ運用容易性の高い構成になります。

この構成は、最初は単一の仮想マシン上で動かしてOK。負荷に応じてデータベースやオブジェクトストレージを外出しする拡張を見据えると、移行が滑らかです。オンプレの余剰サーバーがあれば完全無料で始められますし、クラウドの無料枠を使う場合もストレージとネットワーク課金に注意すれば想定外の出費を避けられます。学習体験の面では、SCORMパッケージの安定動作とH5Pの軽快なインタラクションが両立し、研修担当が非エンジニアであっても運用しやすいのが利点です。セキュリティは無料でも妥協しない。通信の暗号化、強固な認証、アップデートの自動化、バックアップの多層化は最初から設計に含めます。パフォーマンスは社内負荷に応じて調整しますが、Redis(セッション/キャッシュ)やOPcache(PHPのバイトコードキャッシュ)の有効化だけでも実効的な改善が得られます。

アーキテクチャの要点と拡張の余地

無料の条件を満たしつつ堅牢さを確保するには、ステートフル(データを保持する)領域の分離が重要です。Moodleのアプリ層はコンテナ化して冪等に再作成できるようにし、ファイル領域は永続ボリュームとして外出し。データベースはPostgreSQLにまとめ、セッションとキャッシュをRedisに寄せ、スケール時にボトルネックが一箇所に集中しない構成にします。TLS証明書の更新忘れは障害に直結するため、ACMEクライアント(certbotやacme.sh)での自動更新をNginxのリロードと組み合わせ、無停止運用を目指すのが定石です。学習データはMoodle標準レポートだけだと分析が難しくなるため、xAPIでLRSに送る導線を確保しておくと、学習行動の分析やROI評価が行いやすくなります。まずはSCORMで開始し、必要に応じてxAPIへ段階的に拡張するのが現実的です。

実装手順とサンプルコード:ライセンス費ゼロの堅実構築

環境はDockerで統一し、Moodle、PostgreSQL、Redis、Nginxを同一ホスト上に載せてスタートします。最初にコンテナ群を定義し、データ永続化のディレクトリを明示します。下記のYAMLは動作に必要な最小限に絞りつつ、本番移行を見据えた構成です(用語の補足:docker-composeは複数コンテナの定義ファイル、ボリュームは永続データの保管領域)。

version: "3.9"
services:
  moodle:
    image: bitnami/moodle:latest
    environment:
      - MOODLE_USERNAME=admin
      - MOODLE_PASSWORD=${MOODLE_PASSWORD}
      - MOODLE_EMAIL=admin@example.com
      - MOODLE_DATABASE_TYPE=pgsql
      - MOODLE_DATABASE_HOST=db
      - MOODLE_DATABASE_PORT_NUMBER=5432
      - MOODLE_DATABASE_NAME=moodle
      - MOODLE_DATABASE_USER=moodle
      - MOODLE_DATABASE_PASSWORD=${DB_PASSWORD}
      - MOODLE_REDIS_HOST=redis
    depends_on:
      - db
      - redis
    volumes:
      - moodle_app:/bitnami/moodle
      - moodle_data:/bitnami/moodledata
    networks:
      - web
  db:
    image: postgres:16
    environment:
      - POSTGRES_DB=moodle
      - POSTGRES_USER=moodle
      - POSTGRES_PASSWORD=${DB_PASSWORD}
    volumes:
      - pg_data:/var/lib/postgresql/data
    networks:
      - web
  redis:
    image: redis:7-alpine
    command: ["redis-server", "--appendonly", "yes"]
    volumes:
      - redis_data:/data
    networks:
      - web
  nginx:
    image: nginx:1.25-alpine
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    ports:
      - "80:80"
      - "443:443"
    depends_on:
      - moodle
    networks:
      - web
volumes:
  moodle_app: {}
  moodle_data: {}
  pg_data: {}
  redis_data: {}
networks:
  web: {}

NginxはTLS終端(HTTPS化)とキャッシュ制御を担います。Let’s Encryptの証明書更新はcertbotやacme.shで自動化し、OCSP staplingを有効にしてハンドシェイクの遅延を抑えます[5]。以下はHTTP/2対応、基本的なキャッシュ、安全なヘッダ付与を含む設定例です。

worker_processes auto;
events { worker_connections 1024; }
http {
  include       /etc/nginx/mime.types;
  sendfile      on;
  server_tokens off;
  proxy_read_timeout 75s;
  server {
    listen 80;
    server_name lms.example.com;
    location /.well-known/acme-challenge/ { root /var/www/html; }
    location / { return 301 https://$host$request_uri; }
  }
  server {
    listen 443 ssl http2;
    server_name lms.example.com;
    ssl_certificate     /etc/letsencrypt/live/lms.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/lms.example.com/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;
    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
    add_header X-Frame-Options DENY;
    add_header X-Content-Type-Options nosniff;
    add_header Referrer-Policy no-referrer-when-downgrade;
    location / {
      proxy_set_header Host $host;
      proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header X-Forwarded-Proto https;
      proxy_pass http://moodle:8080;
      proxy_buffering on;
      proxy_buffers 16 16k;
      proxy_busy_buffers_size 64k;
    }
    location ~* \.(css|js|jpg|jpeg|png|gif|svg|woff2?)$ {
      expires 7d;
      access_log off;
      proxy_pass http://moodle:8080;
    }
  }
}

研修教材の流通性を高めるうえで、SCORM(eラーニングのコンテンツ交換規格)の理解は避けて通れません。最小構成のimsmanifest.xmlを把握しておくと、ビルド時の検証やCIへの組み込みが容易になります。次はSCORM 1.2に準拠した極小のマニフェスト例です。

<?xml version="1.0" encoding="UTF-8"?>
<manifest identifier="com.example.course" version="1.0"
  xmlns="http://www.imsproject.org/xsd/imscp_rootv1p1p2"
  xmlns:adlcp="http://www.adlnet.org/xsd/adlcp_rootv1p2"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.imsproject.org/xsd/imscp_rootv1p1p2 imscp_rootv1p1p2.xsd
  http://www.adlnet.org/xsd/adlcp_rootv1p2 adlcp_rootv1p2.xsd">
  <organizations default="org1">
    <organization identifier="org1">
      <item identifier="item1" identifierref="res1" isvisible="true">
        <title>Intro</title>
      </item>
    </organization>
  </organizations>
  <resources>
    <resource identifier="res1" type="webcontent" adlcp:scormtype="sco" href="index.html">
      <file href="index.html" />
    </resource>
  </resources>
</manifest>

行動データを細かく収集したい場合はxAPI(学習行動を「誰が・何を・どうした」で記録する規格)を採用し、無料のLRSと組み合わせます。xAPIステートメントはJSONで表現され、学習者の行為を動詞とオブジェクトで記録します。以下は学習者が小テストに合格したことを記録する最小の例です。

{
  "actor": { "mbox": "mailto:alice@example.com", "name": "Alice" },
  "verb": { "id": "http://adlnet.gov/expapi/verbs/passed", "display": {"en-US": "passed"} },
  "object": { "id": "https://lms.example.com/courses/intro/quiz1" },
  "result": { "score": { "scaled": 0.92 }, "success": true, "completion": true },
  "context": { "platform": "Moodle" },
  "timestamp": "2025-08-30T12:34:56Z"
}

アプリケーションからLRSに送る実装は、言語を問わずHTTPで完結します。Node.jsでの送信例を挙げると、認証ヘッダを付与してxAPIのエンドポイントにPOSTするだけです。例ではfetchを使い、再試行とエラー処理を簡潔に実装しています。

import fetch from "node-fetch";

const LRS_ENDPOINT = process.env.LRS_ENDPOINT; // e.g. https://lrs.example.com/data/xAPI/statements
const LRS_KEY = process.env.LRS_KEY;
const LRS_SECRET = process.env.LRS_SECRET;

function basicAuth(key, secret) {
  return "Basic " + Buffer.from(`${key}:${secret}`).toString("base64");
}

async function postStatement(statement) {
  for (let i = 0; i < 3; i++) {
    const res = await fetch(LRS_ENDPOINT, {
      method: "POST",
      headers: {
        "Authorization": basicAuth(LRS_KEY, LRS_SECRET),
        "X-Experience-API-Version": "1.0.3",
        "Content-Type": "application/json"
      },
      body: JSON.stringify([statement])
    });
    if (res.ok) return await res.json();
    const text = await res.text();
    if (res.status >= 500) continue; // retry on server error
    throw new Error(`LRS error ${res.status}: ${text}`);
  }
  throw new Error("LRS unreachable after retries");
}

const stmt = {
  actor: { mbox: "mailto:alice@example.com", name: "Alice" },
  verb: { id: "http://adlnet.gov/expapi/verbs/experienced", display: {"en-US": "experienced"} },
  object: { id: "https://lms.example.com/courses/sec101" },
  timestamp: new Date().toISOString()
};

postStatement(stmt).then(console.log).catch(console.error);

無料で始めても運用を自動化しないとコストが跳ねます。CI/CD(継続的インテグレーション/デリバリー)でMoodleプラグインの更新やテーマのデプロイを半自動化しておくと、人的負荷を抑えられます。GitHub Actionsを用いてイメージのビルドとリモートホストへの更新を行う最小のワークフローは次の通りです。

name: deploy-lms
on:
  push:
    branches: [ main ]
jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Build image
        run: docker build -t ghcr.io/org/moodle:${{ github.sha }} .
      - name: Login GHCR
        run: echo $CR_PAT | docker login ghcr.io -u $GITHUB_ACTOR --password-stdin
        env:
          CR_PAT: ${{ secrets.CR_PAT }}
      - name: Push image
        run: docker push ghcr.io/org/moodle:${{ github.sha }}
      - name: Deploy via SSH
        uses: appleboy/ssh-action@v1.0.3
        with:
          host: ${{ secrets.HOST }}
          username: ${{ secrets.USER }}
          key: ${{ secrets.SSH_KEY }}
          script: |
            docker pull ghcr.io/org/moodle:${GITHUB_SHA}
            docker compose down
            sed -i "s|image: .*|image: ghcr.io/org/moodle:${GITHUB_SHA}|" docker-compose.yml
            docker compose up -d --remove-orphans

コンテンツ制作は無料のH5Pが強力です。Moodleのプラグイン画面からH5Pを有効化し、ブラウザ上でクイズ、ドラッグ&ドロップ、インタラクティブビデオを組み立てられます[3]。業務知識の暗黙知を形式知化する際は、記憶テストだけでなく判断を伴う分岐シナリオを用意すると、現場で効く学習体験になります。作成したH5PはSCORMと併用しても問題はなく、評価方法や追跡の粒度に応じて使い分ければ、無料でも十分な学習設計が可能です[4]。

セキュリティと可用性:無料でも妥協しない基本設計

無料ソフトウェアが脆弱というわけではありません。多くの事故は設定の甘さから生じます。通信は必ずTLSで暗号化し[5]、管理者アカウントは多要素認証を前提に運用します。バックアップはデータベースの論理ダンプとファイル領域のスナップショットを分け、別リージョン相当の保管先に複製しておくと、ランサムウェアや誤削除にも耐えられます。Moodleのアップデートは小刻みに適用し、プラグインの互換性をステージングで検証する流れを定常化してください。まずは攻撃面を狭め、認可ポリシーを厳格にし、監査ログの保存期間をビジネス要件に合わせて設定することが要点です。

パフォーマンス、計測、ROI:無料で成果を出す運用術

無料で構築しても、学習体験が遅ければ現場には浸透しません。アプリケーション側ではOPcacheの有効化、Redisセッション、静的アセットのキャッシュ、画像の適度な圧縮といった基本最適化が直ちに効きます。インフラ側ではCPUの高クロックコアを優先し、ディスクIOのボトルネックを避けるためにデータ領域を別ボリュームに分離します。初期のSLA目安として、コース一覧とクイズ開始のTTFBは200ms台、95パーセンタイルの応答時間は800ms以下を置くと、体感遅延は許容範囲に収まります。実測はk6やJMeterでスクリプト化し、コース閲覧、SCORM起動、H5Pインタラクション、クイズ提出といったシナリオを混在させて負荷を与えると実態に近づきます。

学習効果の可視化は、無料環境の価値を経営に伝えるための橋渡しになります。Moodleの成績データにxAPIの行動データを重ねると、受講の滞留箇所や、上司のフィードバックと合格率の関係が見えてきます。学習完了率、合格率、受講時間、再試験回数といった指標に加え、業務KPIとの相関が確認できれば、研修のROIは定量化できます。無料の堅牢な実装によりライセンス費を抑制できた分、現場のOJTやマイクロラーニングに時間を再配分でき、結果としてオンボーディング期間の短縮や品質事故の減少が見込めます。こうした学習分析の設計例はケーススタディで紹介しています。

よくあるつまずきと回避策:無料ゆえの落とし穴

無料の前提で設計すると、サポート窓口がない不安から設定を保守的に寄せすぎてしまい、パフォーマンスを犠牲にすることがあります。キャッシュを恐れず使い、更新時に無効化を正確に行うことで整合性と速度を両立させましょう。別のよくある失敗は権限設計の曖昧さです。研修担当に管理者権限を広く与えてしまうと設定変更がスプロールしがち。Moodleのロール定義を活用し、コース作成、受講者管理、評価設定を職能ごとに分離する運用ルールを文書化すると、無料のシステムでも秩序を保てます。最後に、コンテンツの著作権と個人情報の扱いを曖昧にすると事故に直結します。素材のライセンスを必ず確認し、学習ログの取り扱いは社内規程に準拠させ、保管期間を明確にしましょう。

移行と定着:小さく始めて素早く学ぶ

無料での立ち上げは、機能が足りないのではなく、使い方がまだ洗練されていないだけという局面が多くあります。最初はオンボーディングの一本に絞り、H5Pとクイズ、簡素なSCORMを組み合わせた最短ループで社内に公開し、行動データから改善サイクルを回します。現場からの改善要求は、UIの文言やコース構成の粒度といった小さな声から始まるため、毎週取り込むリズムを作ると、無料でも十分な満足度に到達できます。スケール段階では、学習のピーク時間帯を避ける配信設計や、クイズの制限時間と同時接続の相関を見直し、並列度に応じてPHP-FPMのchild数やデータベース接続数を調整します。トラフィックがさらに伸びたら、オブジェクトストレージやCDNの活用も検討の余地があります。クラウドの無料枠を活かせば、静的アセット配信は追加ライセンス費なしで応答性を上げられます。

社内ガバナンスとナレッジの蓄積

無料の仕組みは、使いこなしの差が成果の差になります。運用プロセス、役割分担、教材ガイドライン、品質チェックリストを軽量に整備し、レポーティングの粒度を揃えると、改善は指数関数的に加速します。教材の再利用を意識し、H5Pのテンプレート化やSCORMの共通スケルトンを社内レポジトリで共有しておくと、研修担当の生産性は大きく向上します。エンジニアチームは自動化と安全性の担保を、研修担当は学習体験の最適化を担い、両者の協働があって初めて、無料でも成果が出る運用に到達します。

まとめ:無料で始め、結果で拡張する

無料で作るeラーニングは妥協の産物ではありません。Moodle、H5P、SCORM、xAPIという標準と成熟したOSSを土台にすれば、ライセンス費0円でも、学習体験、可観測性、運用性を兼ね備えた環境が実現します。小さく始めて、学習データで改善し、現場適合度を高めていく。成果が見えたところで必要な箇所だけ有償サービスを足す段階的拡張は、全体最適と費用対効果の両立に有効です。次に取りかかるとしたら、どの一本の研修から始めますか。オンボーディング、情報セキュリティ、あるいはプロダクト基礎。今日、最初のH5Pを作り、Moodleに載せ、テスト受講してみてください。無料で動く仕組みが回り始めた瞬間から、学びの改善サイクルは止まらなくなります。

参考文献

  1. LinkedIn. Big results on a small budget: How e-learning levels the field. https://www.linkedin.com/pulse/big-results-small-budget-how-e-learning-levels-field—s1b0e#:~:text=,and%20cheaper%20by%20going%20digital
  2. Moodle.com. 200 million education resources on Moodle sites (and other global stats). https://moodle.com/news/200-million-education-resources-on-moodle-sites/#:~:text=Right%20now%2C%20worldwide%20there%20are%3A
  3. Moodle Docs. Interactive Content – H5P activity. https://docs.moodle.org/38/en/Interactive_Content_-_H5P_activity#:~:text=Free%20to%20use
  4. Opensource.com. Creating HTML5 content with H5P. https://opensource.com/article/16/11/creating-html5-content-h5p#:~:text=H5P%20is%20a%20free%20and,com%2C%20who
  5. Let’s Encrypt / Internet Security Research Group. About Let’s Encrypt and free TLS certificates. https://www.lencr.org/th/#:~:text=Let%27s%20Encrypt%20is%20a%20Certificate,nonprofit%20Internet%20Security%20Research%20Group