Rhel9互換Linux + DockerでWebアプリ環境を構築する
1. はじめに
投稿主は現在「Nginx」+「ElasticSearch」環境でインフラ構築をしています。
今回勉強のため、「Apache」+「PostgreSQL」+「Flask」環境、おまけで「BIND9」でWebアプリ構築を目指します。
何を作るか・・・家計簿か、母親から頼まれた白色確定申告か・・・決まっていません。
本記事では、OSのインストールが完了した直後の状態から、Dockerでのコンテナ起動までの手順をまとめます。
2. システム環境
OS: AlmaLinux 9.x (x86_64)
User: 管理権限を持つ一般ユーザー(sudo権限あり)
前提: インターネット接続が可能なこと
構成はざっとこんな感じ
3. ステップ1:Dockerfileの作成
まずは各サービスの設計図を作成します。私は「/home/admin/Docker.d/」配下に作りました。
後段でも記載しますが、ホスト側で設定やログを管理する際に、ハマりがちなのは権限設定です。
ですので今回は、「UID:GID」を指定して起動させます。
※DNSのみ25で固定のようなので25にしています。(UID:GID10053を指定しても25で起動してきました。)
ビルド時の名前解決エラーを防ぐために docker-compose.yml で network: host を指定します。
FROM almalinux:9
# httpdのインストール
RUN dnf -y update && \
dnf -y install httpd && \
dnf clean all
# UID 10080 で実行するための権限調整
RUN mkdir -p /run/httpd && \
chown -R 10080:10080 /var/www/html /var/log/httpd /run/httpd /etc/httpd
# 80番ポートで待機
EXPOSE 80
# コンテナ起動時にhttpdをフォアグラウンドで実行
CMD ["/usr/sbin/httpd", "-D", "FOREGROUND"]
FROM almalinux:9
# PythonとDB接続用ライブラリ(libpq)のインストール
RUN dnf -y install python3 python3-pip python3-devel gcc libpq-devel && \
dnf clean all
WORKDIR /app
# FlaskとPostgreSQLアダプタのインストール
RUN pip3 install flask psycopg2-binary
# 実行権限の調整
RUN chown -R 15000:15000 /app
EXPOSE 5000
# app.pyを起動
CMD ["python3", "app.py"]
FROM almalinux:9
# PostgreSQLサーバーとユーティリティのインストール
RUN dnf install -y postgresql-server bind-utils && \
dnf clean all
# 必要なパッケージのインストール
# glibc-locale-source: ロケールを生成するために必要
# glibc-langpack-en / ja: 言語パック
RUN dnf install -y postgresql-server glibc-locale-source glibc-langpack-en glibc-langpack-ja && \
dnf clean all
# ロケールの生成(これをしないと conf の en_US.UTF-8 でエラーが出る)
RUN localedef -i en_US -f UTF-8 en_US.UTF-8 && \
localedef -i ja_JP -f UTF-8 ja_JP.UTF-8
# 1. 実行ユーザー(15432)の作成(名前は管理上 postgres のまま数字を固定)
RUN groupadd -g 15432 postgres_admin && \
useradd -u 15432 -g postgres_admin -m postgres_admin
# 2. 初期化用の一時ディレクトリで initdb を実行
RUN mkdir -p /tmp/initdb && \
chown -R 15432:15432 /tmp/initdb && \
su - postgres_admin -c "/usr/bin/initdb -D /tmp/initdb"
# 3. ディレクトリ準備と権限設定
RUN mkdir -p /var/lib/postgresql/data /etc/postgresql /var/log/pgsql /var/run/postgresql && \
# 設定ファイルを /etc/postgresql へ移動
mv /tmp/initdb/postgresql.conf /etc/postgresql/ && \
mv /tmp/initdb/pg_hba.conf /etc/postgresql/ && \
mv /tmp/initdb/postgresql.auto.conf /etc/postgresql/ && \
# 残りのデータ(ベース等)を本来の場所へ
mv /tmp/initdb/* /var/lib/postgresql/data/ || true && \
rm -rf /tmp/initdb && \
# 所有者を一気に変更
chown -R 15432:15432 /var/lib/postgresql /etc/postgresql /var/log/pgsql /var/run/postgresql
# 4. 外部接続を許可する設定の追記(pg_hba.confとpostgresql.conf)
RUN echo "host all all 0.0.0.0/0 md5" >> /etc/postgresql/pg_hba.conf && \
echo "listen_addresses = '*'" >> /etc/postgresql/postgresql.conf
EXPOSE 5432
# 決めたUIDで実行
USER 15432
# 設定ファイルの場所を明示して起動
CMD ["/usr/bin/postgres", "-D", "/var/lib/postgresql/data", "-c", "config_file=/etc/postgresql/postgresql.conf"]
version: '3.8'
# コンテナ間で通信するための共通ネットワーク設定
networks:
app_net:
# ネットワークの名前を固定
name: private_app_net
# コンテナ同士を繋ぐブリッジ方式を採用
driver: bridge
ipam:
config:
# このネットワーク内で使用するIPアドレスの範囲
- subnet: 172.16.10.0/24
services:
# --- 1. データベース (PostgreSQL) ---
db:
build:
# ビルドを実行するディレクトリ
context: .
# データベース専用の設計図を指定
dockerfile: Dockerfile.postgres
#ビルド時にホストのネットワークを使う
network: host
# コンテナを識別するための名前
container_name: db.app.local
# セキュリティのため、作成した専用ユーザーのIDで動かす
user: "15432:15432"
environment:
# 管理ユーザー名
- POSTGRES_USER=dbadmin
- POSTGRES_PASSWORD=password
- POSTGRES_DB=shinkoku_db
# システム時間を日本標準時に設定
- TZ=Asia/Tokyo
# データベースのロケールを日本語に設定
- LANG=ja_JP.UTF-8
# volumes:
# # データベースの本体(データ)を保存
# - /var/lib/pgsql/data:/var/lib/postgresql/data:Z
# # データベースの設定ファイルをマウント
# - /etc/postgresql:/etc/postgresql:Z
# # 実行ログを保存
# - /var/log/pgsql:/var/log/pgsql:Z
# # 起動に必要なロックファイル用ディレクトリ
# - /var/run/postgresql:/var/run/postgresql:Z
# PCが再起動した際に自動でコンテナを立ち上げる
restart: always
networks:
app_net:
# データベースの固定住所を指定
ipv4_address: 172.16.10.10
# --- 2. Flask アプリ ---
flask_app:
build:
context: .
# Python/Flask用の設計図を指定
dockerfile: Dockerfile.flask
#ビルド時にホストのネットワークを使う
network: host
container_name: flask.app.local
# アプリ専用ユーザーのIDで動かす
user: "15000:15000"
environment:
# データベースへの接続情報を指定(コンテナ名で指定可能)
- DATABASE_URL=postgresql://dbadmin:password@db.app.local:5432/shinkoku_db
- TZ=Asia/Tokyo
- LANG=ja_JP.UTF-8
volumes:
# Pythonのプログラムコードをコンテナ内に持ち込む
# - /opt/flask/app:/app:Z
# データベースが起動完了してからこのコンテナを動かす
depends_on:
- db
# PC起動時に自動立ち上げ
restart: always
networks:
app_net:
# アプリの固定住所を指定
ipv4_address: 172.16.10.20
# --- 3. Apache (Webサーバー/リバースプロキシ) ---
apache:
build:
context: .
# Apache用の設計図を指定
dockerfile: Dockerfile.apache
#ビルド時にホストのネットワークを使う
network: host
container_name: www.app.local
# Webサーバー専用ユーザーのIDで動かす
user: "10080:10080"
environment:
- TZ=Asia/Tokyo
- LANG=ja_JP.UTF-8
ports:
# iPhone等からアクセスする80番ポートをコンテナに繋ぐ
- "80:80"
# volumes:
# # アクセスをFlaskに流すための転送設定
# - /etc/httpd/conf.d/vhosts.conf:/etc/httpd/conf.d/vhosts.conf:Z
# # 公開用のWeb静的ファイルを保存
# - /var/www/html:/var/www/html:Z
# # アクセスログを保存
# - /var/log/httpd:/var/log/httpd:Z
# Flaskアプリが起動完了してから動かす
depends_on:
- flask_app
# PC起動時に自動立ち上げ
restart: always
networks:
app_net:
# Webサーバーの固定住所を指定
ipv4_address: 172.16.10.30
# --- 4. DNS (---
dns:
build:
context: .
# DNSサーバー用の設計図を指定
dockerfile: Dockerfile.bind
#ビルド時にホストのネットワークを使う
network: host
container_name: dns.app.local
# DNSサーバーはUID:GID25で動く
environment:
- TZ=Asia/Tokyo
- LANG=ja_JP.UTF-8
ports:
# DNS通信用のポート(TCP/UDP両方)
- "53:53/tcp"
- "53:53/udp"
# volumes:
# # --- 1. 設定ファイル (読み取り用) ---
# - /etc/bind/named.conf:/etc/named.conf:Z
# - /etc/bind/named.conf.local:/etc/named.conf.local:Z
# - /etc/bind/named.ca:/etc/named.ca:Z
# - /etc/bind/named.rfc1912.zones:/etc/named.rfc1912.zones:Z
# - /etc/bind/named.root.key:/etc/named.root.key:Z
#
# # --- 2. データディレクトリ (書き込み用) ---
# # ホストの /var/lib/bind を コンテナの /var/named にマウント
# - /var/lib/bind:/var/named:Z
#
# # --- 3. ログ ---
# - /var/log/named:/var/log/named:Z
# PC起動時に自動立ち上げ
restart: always
networks:
app_net:
# DNSサーバーの固定住所を指定
ipv4_address: 172.16.10.40
volumes:をコメントにしている理由ですが、コンテナ内にできたデフォルト設定ファイルやログを抜き出すためです。
4. ステップ2:ホスト側ディレクトリの準備
設定やデータを永続化するため、ホスト側にマウントポイントを作成します。
# Web, DNS, DB, App の各ディレクトリを一括作成
mkdir -p /etc/httpd/conf.d /etc/bind/zones /etc/postgresql
mkdir -p /var/lib/pgsql/data /var/www/html/ /home/admin/docker.d/app
mkdir -p /var/log/httpd /var/log/pgsql /var/log/named
mkdir -p /opt/flask/app
5. ステップ3:コンテナの仮起動と設定ファイル抽出
現状だと、ホスト側にはマウントポイントのディレクトリがあるだけで、デフォルトの設定ファイルがない状態です。
そのため、コンテナを仮起動しコンテナ内のデフォルト設定ファイルを抜き出します。
作業は /home/admin/docker.d/ で実行します。
1. DNS (BIND)
イメージ内にある「ルートヒント(named.ca)」や「初期設定」を救出します。
# 仮起動
docker run --name temp-dns -d dockerd-dns
# ファイルの吸い出し
docker cp temp-dns:/etc/named.conf /etc/bind/
docker cp temp-dns:/etc/named.rfc1912.zones /etc/bind/
docker cp temp-dns:/etc/named.root.key /etc/bind/
docker cp temp-dns:/var/named/named.ca /var/lib/bind/
# コンテナ停止・削除
docker rm -f temp-dns
2. Database (PostgreSQL)
PostgreSQLは initdb で生成された初期データ一式が必要です。
# 仮起動
docker run --name temp-db -d dockerd-db
# 設定ファイルとデータ一式を丸ごとコピー
docker cp temp-db:/etc/postgresql/postgresql.conf /etc/postgresql/
docker cp temp-db:/etc/postgresql/pg_hba.conf /etc/postgresql/
docker cp temp-db:/var/lib/postgresql/data/. /var/lib/pgsql/data/
# コンテナ停止・削除
docker rm -f temp-db
3. Web (Apache)
標準の httpd.conf やディレクトリ構造をコピーします。
# 仮起動
docker run --name temp-web -d dockerd-apache
# 設定ファイルのコピー
docker cp temp-web:/etc/httpd/conf/httpd.conf /etc/httpd/
docker cp temp-web:/etc/httpd/conf.d/. /etc/httpd/conf.d/
# コンテナ停止・削除
docker rm -f temp-web
6. ステップ4:実行ユーザーの同期(重要!)
「ホスト設定」構成で最もハマるのが権限です。コンテナ内のユーザーUIDとホスト側の所有者を一致させます。
# 各コンテナの実行ユーザーUIDに合わせて所有権を変更
# DNS (BIND)
chown -R 25:25 /etc/bind /var/log/named /var/run/named
# Web (Apache)
chown -R 10080:10080 /etc/httpd /var/www/html/www /var/log/httpd
# App (Flask)
chown -R 15000:15000 /opt/flask/app/
# DB (PostgreSQL)
chown -R 15432:15432 /var/lib/pgsql/data /etc/postgresql /var/log/pgsql /var/run/postgresql
# 権限の一括設定
chmod -R 755 /etc/httpd /etc/bind /etc/postgresql /var/www/html/www /home/admin/docker.d/app /var/log/httpd /var/log/pgsql /var/log/named /var/lib/pgsql/data
反映確認
ls -ld /etc/httpd /etc/bind /etc/postgresql /var/log/httpd /var/log/pgsql /var/log/named
drwxr-xr-x 3 25 25 61 1月 23 10:52 /etc/bind
drwxr-xr-x. 5 10080 10080 54 11月 22 19:49 /etc/httpd
drwxr-xr-x 2 15432 15432 75 1月 23 11:12 /etc/postgresql
drwxr-xr-x. 2 10080 10080 41 11月 22 19:42 /var/log/httpd
drwxr-xr-x 2 25 25 6 1月 23 09:41 /var/log/named
drwxr-xr-x. 2 15432 15432 4096 1月 23 11:59 /var/log/pgsql
7. ステップ5:PostgreSQLの特殊設定
PostgreSQLはデータディレクトリの権限に非常に厳格です。また、Dockerマウント時のロックファイルエラーを回避するための設定変更が必要です。
権限の再設定
chmod 700 /var/lib/pgsql/data
postgresql.conf の編集
/etc/postgresql/postgresql.conf を開き、以下の項目を修正します。
# 外部(コンテナネットワーク内)からの接続をすべて許可する設定
listen_addresses = '*'
# クライアントが送受信するデータの文字コードをUTF-8に指定
client_encoding = 'UTF-8'
# データベース内部で扱うタイムゾーンを日本標準時に設定
timezone = 'Asia/Tokyo'
# ログに出力される時刻を日本標準時に設定
log_timezone = 'Asia/Tokyo'
# 日付の表示形式を ISO 形式(YYYY-MM-DD)に設定
datestyle = 'iso, ymd'
# ログに記録されるエラーメッセージの言語設定(英語の方がエラー解決しやすいため)
lc_messages = 'en_US.UTF-8'
# 通貨単位の書式設定(円記号など)を日本向けに設定
lc_monetary = 'ja_JP.UTF-8'
# 数値の区切り記号(カンマやピリオド)を日本向けに設定
lc_numeric = 'ja_JP.UTF-8'
# 曜日の名前や日付の単語を日本向けに設定
lc_time = 'ja_JP.UTF-8'
# データベースが待ち受けるポート番号(標準の5432番)
port = 5432
# 同時に接続できる最大クライアント数
max_connections = 100
# PostgreSQLが使用する共有メモリのバッファサイズ
shared_buffers = 128MB
# ログの自動収集機能を有効化
logging_collector = on
# ログファイルを出力するディレクトリ(ホスト側と同期させたパス)
log_directory = '/var/log/pgsql'
pg_hba.conf の編集
/etc/postgresql/pg_hba.conf の末尾に追記し、Dockerネットワーク内からの接続を許可します。
# 【ローカル接続】サーバー内(Unixドメインソケット)からの接続はすべて無条件で許可
local all all trust
# 【IPv4 ループバック】自分自身(127.0.0.1)からの接続はすべて無条件で許可
host all all 127.0.0.1/32 trust
# 【IPv6 ループバック】自分自身(::1)からの接続はすべて無条件で許可
host all all ::1/128 trust
# 【重要:Dockerネットワーク】
# 指定したサブネット(172.16.10.0/24)からのアクセスに対し、
# すべてのユーザー・データベースへの接続を「パスワード認証(md5)」で許可
host all all 172.16.10.0/24 md5
9. ステップ7:flaskデフォルトアプリの作成
コンテナを立ち上げるためのデフォルトのアプリを作成します。
※Apacheのリバースプロキシ設定がないと確認できませんが、コンテナ起動のためだけに作成しています。
次回以降のApacheの設定確認でも使用します。
from flask import Flask
import os
app = Flask(__name__)
@app.route('/')
def hello():
return "<h1>Flask is Running!</h1><p>Location: /opt/flask/app/app.py</p>"
if __name__ == "__main__":
app.run(host='0.0.0.0', port=5000)
10. ステップ8:コンテナ4台の一括立ち上げ
docker-compose.yml内のvolumes:のコメントを外し、ビルドと起動を行います。
cd /home/admin/docker.d/
docker compose up -d --build
最終確認
docker compose ps
全てのサービスが
Upになっていれば成功です。
