フルスクラッチで作成したWebアプリをDockerイメージ化してAppRunで動かすしてみよう(第1回)

ネットアシスト開発チームのyu-kinjoです。今回は、WordPress等の既存のイメージを利用するのではなく、自前でフルスクラッチで作成したWebアプリケーションをDockerイメージ化して、さくらのクラウドの「AppRun」で動かすことを目指します。実際に自前で開発したWebアプリをAppRunで動かすにはどうしたらよいか、イメージがつかめることを目指します。

目次

AppRunとは

AppRunは、さくらのクラウドが提供するコンテナ実行サービスです。Dockerコンテナイメージをデプロイ~実行する事で、任意のWebアプリケーションを動作させるアプリケーション基盤を提供しています。

さくらのクラウド AppRunでWordPressを構築する手順|Dockerコンテナでデプロイ検証」という記事を公開しておりますので、こちらでAppRunを用いて既存のWordPressイメージをデプロイ~実行するうえでの内容をご確認いただけます。

あわせて読みたい
さくらのクラウド AppRunでWordPressを構築する手順|Dockerコンテナでデプロイ検証 こんにちは、UOZUです! 先日、クラウドサーバー上でWordPressの検証やベンチマークを行いましたが、今回は「AppRun」を使ってWordPressを構築してみたいと思います。 A...

Webアプリケーションの作成

Webアプリケーションの設計と構成

今回は既存のイメージではなく、フルスクラッチでWebアプリケーションを作成していきます。Webアプリケーションは「DBにアクセスしその記事内容を一覧・詳細表示できる」というもので、以下の内容で作成しました。

  • Webアプリ本体はPHP 8.4で実装します。
  • TOPページではRDBMS(PostgreSQL)上の contents テーブルを読み込んで記事一覧表示。
  • 記事をクリックすると記事IDから contents テーブル内の任意のレコードを読み込んで記事詳細ページが表示。
  • Webアプリケーションフレームワークを使わず、以下の3ファイルで完結する非常にシンプルな構成にしています。
    • index.php (TOPの記事一覧ページ)
    • article.php (記事詳細ページ)
    • db.php (DB接続ファイル。上記2ファイルから require される)

また、コンテナやミドルウェアも含めた構成は以下のようになります。

  • Webサーバーとして Apache、PHPの実行方法として PHP-FPM の組み合わせで動作させます。
  • PostgreSQLはコンテナ内には置かず、さくらのクラウドの「データベースアプライアンス」を利用する前提とします。これにより実行コンテナ台数の可変や、コンテナの停止時のデータの永続化に配慮します。また、アプライアンス側の機能でバックアップや冗長化も利用できるようになります。
  • DB接続情報はコンテナ内に含めず、コンテナ起動時にパラメータとして渡せるようにします。

Webアプリケーション側ファイルの詳細

index.php

トップの記事一覧ページです。contentsテーブルの内容を元に記事をリスト表示します。

<?php
require __DIR__ . '/db.php';

$articles = $pdo
    ->query('SELECT id, title, create_ts FROM contents ORDER BY create_ts DESC')
    ->fetchAll(PDO::FETCH_ASSOC);
?>
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>記事一覧</title>
<style>
  body { font-family: sans-serif; max-width: 800px; margin: 2rem auto; padding: 0 1rem; }
  h1 { border-bottom: 2px solid #333; padding-bottom: .5rem; }
  ul { list-style: none; padding: 0; }
  li { padding: .6rem 0; border-bottom: 1px solid #ddd; }
  li a { font-size: 1.1rem; text-decoration: none; color: #0066cc; }
  li a:hover { text-decoration: underline; }
  .date { color: #888; font-size: .85rem; margin-left: .8rem; }
</style>
</head>
<body>
<h1>記事一覧</h1>
<?php if (empty($articles)): ?>
  <p>記事がありません。</p>
<?php else: ?>
  <ul>
  <?php foreach ($articles as $a): ?>
    <li>
      <a href="article.php?id=<?= (int)$a['id'] ?>"><?= htmlspecialchars($a['title']) ?></a>
      <span class="date"><?= htmlspecialchars($a['create_ts']) ?></span>
    </li>
  <?php endforeach; ?>
  </ul>
<?php endif; ?>
</body>
</html>

article.php

記事詳細ページです。URL上でクエリ指定されたidを元にcontentsテーブルから記事を取得し、記事詳細を表示します。

<?php
require __DIR__ . '/db.php';

$id = (int)($_GET['id'] ?? 0);
if ($id <= 0) {
    header('Location: index.php');
    exit;
}

$stmt = $pdo->prepare('SELECT * FROM contents WHERE id = ?');
$stmt->execute([$id]);
$article = $stmt->fetch(PDO::FETCH_ASSOC);

if (!$article) {
    http_response_code(404);
    exit('記事が見つかりません。');
}
?>
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title><?= htmlspecialchars($article['title']) ?></title>
<style>
  body { font-family: sans-serif; max-width: 800px; margin: 2rem auto; padding: 0 1rem; }
  h1 { border-bottom: 2px solid #333; padding-bottom: .5rem; }
  .meta { color: #888; font-size: .85rem; margin-bottom: 1.5rem; }
  .body { line-height: 1.8; white-space: pre-wrap; }
  a { color: #0066cc; }
</style>
</head>
<body>
<p><a href="index.php">← 一覧に戻る</a></p>
<h1><?= htmlspecialchars($article['title']) ?></h1>
<p class="meta">
  作成: <?= htmlspecialchars($article['create_ts']) ?>
  &nbsp;/&nbsp;
  更新: <?= htmlspecialchars($article['update_ts'] ?? '') ?>
</p>
<div class="body"><?= htmlspecialchars($article['body']) ?></div>
</body>
</html>

db.php

DB接続処理です。DB接続情報は getenv() を利用し、環境変数から取得するようにしています。

<?php
// DB接続設定
$dsn = sprintf(
    'pgsql:host=%s;dbname=%s',
    getenv('DB_HOST') ?: 'localhost',
    getenv('DB_NAME') ?: 'example'
);
try {
    $pdo = new PDO(
        $dsn,
        getenv('DB_USER') ?: 'root',
        getenv('DB_PASS') ?: '',
        [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
    );
} catch (PDOException $e) {
    http_response_code(500);
    exit('DB接続エラー: ' . htmlspecialchars($e->getMessage()));
}

Dockerのイメージビルド準備

Dockerfileの作成

コンテナイメージをビルドするための設定ファイルである「Dockerfile」を作成していきます。今回はPHP公式のイメージである「php:8.4-fpm」を元に、必要な肉付けをしていきます。

FROM php:8.4-fpm

# Apache + supervisor + PDO PostgreSQL を導入
RUN apt-get update && apt-get install -y \
    apache2 \
    supervisor \
    libpq-dev \
    libonig-dev \
    && rm -rf /var/lib/apt/lists/*

# PHPの拡張を有効化
RUN docker-php-ext-install pdo pdo_pgsql mbstring opcache

# Apacheのモジュールを有効化
RUN a2enmod proxy_fcgi setenvif rewrite

# Apacheの設定、PHP-FPMの設定、PHPの設定、supervisordの設定をコピーする
COPY conf/apache/000-default.conf /etc/apache2/sites-available/000-default.conf
COPY conf/php-fpm/www.conf /usr/local/etc/php-fpm.d/www.conf
COPY conf/supervisord.conf /etc/supervisor/conf.d/app.conf
COPY conf/php/php.ini /usr/local/etc/php/conf.d/app.ini

# Webアプリケーション本体をコピーする
COPY src/ /var/www/html/
RUN chown -R www-data:www-data /var/www/html

EXPOSE 80

CMD ["/usr/bin/supervisord", "-n", "-c", "/etc/supervisor/conf.d/app.conf"]

FROM php:8.4-fpm で元になるイメージを読み込んだ後に、必要なPHP拡張を導入するためのライブラリの導入、Apacheや「supervisor」の導入を行っています。今回はこの「supervisor」がポイントです。後ほど説明します。

その後は必要なPHP拡張の有効化、php-fpm設定の有効化、あらかじめ用意しておいた設定ファイルのコピー、Webアプリケーション本体のApacheドキュメントルートへのコピーなどを行います。

EXPOSE 80 でHTTP80で公開する設定を行い、最後にこのコンテナが起動するべきプログラムである supervisord を起動しています。

supervisorとは

Dockerコンテナでは原則としてフォアグラウンドで起動しっぱなしにするプロセスは単一のプロセスとなります。しかし、今回はApacheとPHP-FPMを両方動作させるコンテナイメージを作ろうとしています。こういった場合に「supervisor」を利用する事で、フォアグラウンドでは supervisor を起動し、その supervisor がApacheおよびPHP-FPMを起動する、という事が出来るようになっています。

上記Dockerfile内でコピーを行っている supervisord.conf の中身はこのようになっています。

[supervisord]
nodaemon=true
logfile=/dev/null
logfile_maxbytes=0

[program:php-fpm]
command=/usr/local/sbin/php-fpm -F
autostart=true
autorestart=true
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0

[program:apache2]
command=/usr/sbin/apache2ctl -D FOREGROUND
autostart=true
autorestart=true
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0

nodaemon=true の部分はsupervisordを非デーモン(つまりフォアグラウンド)で動作させる指定です。更に、supervisordが子プロセスとしてphp-fpmおよびapache2を起動するような設定になっています。

そのほかの設定ファイル

Dockerfile内で指定している、イメージ内にコピーしているそのほかの設定ファイルは以下のようになっています。

000-default.conf

Apacheの設定ファイルです。ドキュメントルートの設定とPHP-FPMとの連携設定を含めています。

<VirtualHost *:80>
    DocumentRoot /var/www/html

    <Directory /var/www/html>
        AllowOverride All
        Require all granted
        DirectoryIndex index.php
    </Directory>

    <FilesMatch "\.php$">
        SetHandler "proxy:fcgi://127.0.0.1:9000"
    </FilesMatch>

    ErrorLog ${APACHE_LOG_DIR}/error.log
    CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>

php.ini

php.iniには今回はタイムゾーンだけ設定しておきます。

[PHP]
date.timezone = Asia/Tokyo

PostgreSQLに追加するテーブル

今の段階ではまだDBを構築しませんが、構築する予定のRDBMSのPostgreSQLには、以下のようなDBとテーブルを作成します。DBとテーブル作成後は記事となるレコードをいくつか登録しておきます。

CREATE DATABASE sakura_app_db
    WITH
    ENCODING = 'UTF8';

CREATE TABLE IF NOT EXISTS contents
(
    id serial NOT NULL,
    title text NOT NULL,
    body text NOT NULL,
    create_ts timestamp without time zone NOT NULL,
    update_ts timestamp without time zone,
    CONSTRAINT contents_pkey PRIMARY KEY (id)
);

INSERT INTO contents (title, body, create_ts) VALUES ('記事1', 'ここに記事の本文が入ります!', '2026-05-08 17:00:00');
INSERT INTO contents (title, body, create_ts) VALUES ('新しい記事', '新しい記事の本文です!', '2026-05-08 17:30:00');

Dockerのイメージビルド

dockerコマンドでイメージのビルドを実行します。-tオプションで指定しているのはイメージ名です。

> docker build --platform linux/amd64 -t sakura_app_run .
[+] Building 14.4s (5/15)                                                                     docker:desktop-linux
 => [internal] load build definition from Dockerfile                                                          0.0s
 => => transferring dockerfile: 978B                                                                          0.0s
 => [internal] load metadata for docker.io/library/php:8.4-fpm                                                2.6s
 => [auth] library/php:pull token for registry-1.docker.io                                                    0.0s
 => [internal] load .dockerignore                                                                             0.0s
 => => transferring context: 2B                                                                               0.0s
~ 以下略 ~

ビルドした結果のイメージ「sakura_app_run」は、ローカル環境でも以下のようにコンテナの作成と起動が可能です。–name オプションで指定している sakura_app_1 がコンテナ名、引数の最後の sakura_app_run がイメージ名です。-e は環境変数の設定で、DB接続情報を環境変数として設定して渡すようにしています。

※環境に応じて追加で引数が必要な場合が有ります。

> docker run -d --name sakura_app_1 -p 8080:80 -e DB_HOST=<DBホストを入力> -e DB_NAME=<DB名を入力> -e DB_USER=<DBユーザー名を入力> -e DB_PASS=<DB-PWを入力> sakura_app_run

起動に成功したらWebブラウザから http://localhost:8080/ でアクセスできます。接続先のDBが構築済みの場合は以下のように記事一覧や記事詳細画面が動作します。未構築の場合でも、DB接続不可の画面が表示されていれば、Webアプリは起動している事が分かります。

コンテナの停止は以下のようにします。

> docker container ls
→表示されるコンテナIDを確認

> docker container stop <コンテナID>

これで目的のオリジナルDockerイメージは完成です。イメージの一覧は以下のコマンドで確認できます。

> docker image ls
                                                                            i Info →   U  In Use
IMAGE                    ID             DISK USAGE   CONTENT SIZE   EXTRA
sakura_app_run:latest    64ecacb2abbc        569MB             0B    U

第2回へ続く

次回はいよいよ、このオリジナルDockerイメージをAppRunで起動していきます。また、データベース部分は「データベースアプライアンス」を組み合わせて利用しますので、次回はこのデータベースアプライアンスの設定も行っていきます。お楽しみに。

お問い合わせ

この記事をシェアする
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

ネットアシスト開発部の yu-kinjo です。

【取得資格】
・さくらのクラウド検定
・AWS Certified Solutions Architect - Associate
・AWS Certified AI Practitioner
・Oracle 認定Javaプログラマ SE6
・JSTQB テスト技術者資格 ファンデーションレベル
・CIW (Certified Internet Webprofessional) ファンデーション
・XML技術者育成推進委員会 XMLマスター ベーシックV2
・基本情報処理技術者
・初級システムアドミニストレータ

目次