Dockerfileとdocker-composeでウェブページをデプロイする


## 1. やりたいこと


git cloneしてdocker compose upしたらデプロイできるようにしたい


## 2. どうやって実現するか


以下をフォルダに置いてdocker compose upするだけです

・ソースコード
・Dockerfile
・docker-compose.yml


## 3. 試行環境


以下の記事で作った環境で実行しています
ホスト: Windows10
実行環境: WSL2 Ubuntu-22.04

https://qiita.com/studio_haneya/items/0bc57d0c21732fc277fd#4-4-docker%E3%82%92%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB%E3%81%99%E3%82%8B


## 4. 前提知識


### 4-1. Dockerfileの書き方


Dockerfileはdockerイメージ1つ分のビルド指示を書くファイルです
以下のコマンドでdockerイメージをビルドするときの指示を書けます


```
FROM:       ベースとするdockerイメージとバージョンを指定します (ubuntu:latestなど)
WORKDIR:    RUNなどのコマンドを実行するワーキングディレクトリを指定します
RUN:        dockerイメージビルド時にdockerのterminalからコマンドを実行します
COPY:       dockerイメージビルド時にホストからdockerにフォルダやファイルをコピーします
ADD:        dockerイメージビルド時にホストからdockerにフォルダやファイルをコピーします
VOLUME:     ホストのフォルダやファイルをdockerイメージにマウントします
EXPOSE:     コンテナがアクセスを許可するポートを書きます
ENV:        環境変数を登録します
ENTRYPOINT: コンテナを起動する際に実行するコマンドを指定します
CMD:        コンテナを起動する際に実行するデフォルト引数を指定します
```


FROMで指定するdockerイメージは<a href="https://hub.docker.com/">docker hub</a>にある公式のイメージを利用することが多いと思います。COPYとADDは似てますがADDは圧縮ファイルを解凍してからコピーするのに対し、COPYはそのままコピーします。ENTRYPOINTとCMDも似てますが、CMDはdocker runするときに引数を指定することで変更されるのに対して、ENTRYPOINTは必ず実行されるコマンドであるという違いがあるようです


Dockerfileについての公式ドキュメントは以下にあります


https://docs.docker.jp/engine/reference/builder.html#builder-format


### 4-2. docker-compose.ymlの書き方


docker-compose.ymlにはdockerコンテナを起動するときの条件を書いておきます。複数Dockerfileを指定して複数のコンテナを同時に起動することもできますのでフロントエンド、バックエンド、DBとまとめて起動する場合も1ファイルにまとめて書いて、docker compose upするだけでまとめて起動することができます


```
services:
  コンテナ名:
    image: "イメージ名"
    ports:
      - "ホスト側のポート:コンテナのポート"
    volumes:
      - ホスト側のパス:コンテナのパス
    environment:
      環境変数名: 環境変数値
  コンテナ名:
    build: Dockerfileのパス 
    ports:
      - "ホスト側のポート:コンテナのポート"
    volumes:
      - ホスト側のパス:コンテナのパス
    environment:
      環境変数名: 環境変数値
    depends_on:
      - コンテナ名
```


portsの書き方については<a href="https://qiita.com/tksugimoto/items/23fcce1b067661e8aa46#host-portcontainer-port-host-ip%E3%82%92%E7%9C%81%E7%95%A5%E3%81%97%E3%81%9F%E5%A0%B4%E5%90%88">この記事</a>が詳しいです


### 4-3. 起動する方法


docker-compose.ymlの内容を実行するにはdocker compose upします


```terminal:terminal
docker compose up
```


--buildオプションを付ければ既にイメージがある状態でもビルドからやり直してくれます


```terminal:terminal
docker compose up --build
```



### 4-4. 起動中のdockerコンテナへの接続


以下のコマンドで起動しているコンテナのterminalにアクセスできます



```terminal:terminal
docker exec -i -t CONTAINER_ID /bin/bash
```


コンテナIDは以下で確認できます


```terminal:terminal
docker container ls
```


上手く動かないときにはこれでterminalに入って確認すると良いと思います


## 5. 静的サイトをデプロイする


ファイル構成は以下のようにしました
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277233/e26a0f4b-711f-e705-3bba-e98ab301848f.png" width=30%>


### 5-1. Dockerfile


静的サイトなのでベースイメージにはhttpd:latestを使いたいと思います
htmlをhtml名のフォルダに入れる想定で、COPY欄にフォルダ内の内容をドキュメントルートにコピーするように設定しました


```dockerfile:Dockerfile
FROM httpd:latest
COPY ./html/ /usr/local/apache2/htdocs/
```


### 5-2. docker-compose.yml


docker-compose.ymlでは上記Dockerfileをbuildして、ポートはdockerコンテナ内で80番のポートを80番のポートとするように設定しました


```docker:docker-compose.yml
version: "1"
services:
  web:
    build: .
    ports: 
      - "80:80"
```


後は表示したいhtmlファイルを書いたら完成です


```html:html/index.html
<html>
    <head>
        <title>ページ</title>
        <link rel="icon" href="data:,">
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=Edge">
        <meta name="viewport" content="width=device-width">
    </head>

    <body>
        ほげほげ
    </body>
</html>
```


### 5-3. 起動


あとはこのフォルダでdocker compose upすれば起動できます


```terminal:terminal
$ docker compose up
[+] Running 1/0
 ✔ Container test2-web-1  Recreated                                                                                0.0s
Attaching to web-1
web-1  | AH00558: httpd: Could not reliably determine the server's fully qualified domain name, using 172.18.0.2. Set the 'ServerName' directive globally to suppress this message
web-1  | AH00558: httpd: Could not reliably determine the server's fully qualified domain name, using 172.18.0.2. Set the 'ServerName' directive globally to suppress this message
web-1  | [Sun Jan 21 14:56:31.806008 2024] [mpm_event:notice] [pid 1:tid 140235577243520] AH00489: Apache/2.4.58 (Unix) configured -- resuming normal operations
web-1  | [Sun Jan 21 14:56:31.806069 2024] [core:notice] [pid 1:tid 140235577243520] AH00094: Command line: 'httpd -D FOREGROUND'
web-1  | 172.18.0.1 - - [21/Jan/2024:14:56:34 +0000] "GET / HTTP/1.1" 200 357
web-1  | 172.18.0.1 - - [21/Jan/2024:14:57:26 +0000] "-" 408 -
```


ブラウザでlocalhostを開くと表示されている筈です


<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277233/6f339beb-a1cb-5d79-5a6d-4d0b9a148bb6.png" width=50%>


## 6. ビルドしたSPAをデプロイする


VueやReactで作ったSPAアプリはビルドしてあれば静的サイトと同じなので、デプロイ時にやることも静的サイトと同じです


### 6-1. Vueプロジェクトを準備する


node.jsが入っている環境で以下を実行するとVueのプロジェクトが作成されます


```terminal:terminal
npm install vue
npm init vue@latest
```


以下でビルドします


```terminal:terminal
npm run build
```


### 6-2. ファイル構成と手順


Dockerfileとdocker-compose.ymlを追加してフォルダ構成は以下のようにしました


<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277233/c6b5da0e-bce8-43c1-72bb-32ea12ed3fe9.png" width=20%>


### 6-3. Dockerfile


```dockerfile:Dockerfile
FROM httpd:latest
COPY ./myapp/dist/ /usr/local/apache2/htdocs/
```

### 6-4. docker-compose.yml


```dockerfile:docker-compose.yml
services:
  web:
    build: .
    ports: 
      - "80:80"
```


### 6-5. 起動する


あとはこのフォルダでdocker compose upすれば
http://localhost にVueアプリが待機します


<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277233/b7dbf414-7cf3-aba9-51ee-6fcb32d5afe7.png" width=50%>


## 7. Djangoで作った動的サイトをデプロイする


次はDjangoでつくった動的サイトをデプロイしてみましょう


### 7-1. Djangoプロジェクトをつくる


Python + Djangoが入っている環境で以下を実行すればプロジェクトの最小構成が作成されます


```terminal:terminal
django-admin startproject mysite
```


requirement.txtを以下で作成します
以下で書き出す場合は必要なパッケージがすべてpipで入っている必要がありますので、condaなどを併用してる方は<a href="https://pypi.org/project/pipreqs/0.1.5/">pipreqs</a>を使うのが良いようです


```terminal:terminal
python -m pip freeze > requirements.txt
```


### 7-2. ファイル構成と手順


Dockerfileとdocker-compose.ymlを追加してフォルダ構成は以下のようにしました


<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277233/2fc1d7c4-e753-10c0-5804-b36698e796c4.png" width=20%>


やらなければならない手順は以下になります


1. apache2をインストールする
2. mod-wsgiをインストールする
3. pythonをインストールする
4. djangoをインストールする
5. apache2の設定ファイルにdjango, mod-wsgiの設定を追加する
6. ソースコードをマウントする


### 7-3. apache2の設定ファイル


apache2の設定に追加する内容は以下のようにしました。<a href="https://docs.djangoproject.com/ja/2.0/howto/deployment/wsgi/modwsgi/">Django公式のドキュメント</a>通りにpythonのパスやDjangoプロジェクトのパスを指示してやります


```apache:apache_django.conf
ServerName localhost:80
WSGIScriptAlias / /var/web/mysite/html/mysite/wsgi.py
WSGIPythonPath /var/web/mysite/html

<Directory /var/web/mysite/html/mysite>
  <Files wsgi.py>
    Require all granted
  </Files>
</Directory>

Alias /static/ /var/web/mysite/html/static/
<Directory /var/web/mysite/html/static>
  Require all granted
</Directory>
```


### 7-4. Dockerfile


<a href="https://hub.docker.com/_/django">Djangoの公式イメージ</a>がdeprecatedになって、代替イメージとしてUbuntuを使うように書かれていますのでUbuntuでやってみたいと思います


```dockerfile:Dockerfile
FROM ubuntu

# アップデート
RUN apt-get update

# apache2, python, mod-wsgiをインストールする
RUN apt-get install -y apt-utils vim curl apache2 apache2-utils apache2-dev
RUN apt-get -y install python3 libapache2-mod-wsgi-py3
RUN ln /usr/bin/python3 /usr/bin/python
RUN apt-get -y install python3-pip

# pythonにdjangoをインストールする
RUN pip install --upgrade pip
RUN pip install django ptvsd

# pythonにmod-wsgiをインストールする
RUN pip install mod_wsgi mod-wsgi-httpd

# apacheの設定ファイルにmod-wsgiの設定を追加
COPY ./apache_django.conf /etc/apache2/sites-available/apache_django.conf
RUN a2ensite apache_django.conf

# Djangoソースコードをマウント
VOLUME ./web/mysite ./var/web/mysite

EXPOSE 80
CMD ["apache2ctl", "-D", "FOREGROUND"]
```


### 7-5. docker-compose.yml


あまりビルドし直したくないのでvolumesでソースコードをマウントしていますが、毎回docker compost up --buildするのであればDockerfileのCOPYでコピーするのでも良いと思います


```docker:docker-compose.yml
services: 
  django-apache2:
    build: .
    container_name: django-apache2
    ports:
      - '80:80'
    volumes: 
      - ./web/mysite:/var/web/mysite/html
```


### 7-6. 起動する


あとはこのフォルダでdocker compose upすれば
http://localhost にDjangoウェブアプリが待機します


<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277233/c6f3d85c-01c0-0afd-a081-5c4f67284529.png" width=50%>


## まとめ


フォルダでdocker compose upするだけでデプロイできるようにしました。フォルダの内容をGithubにpushしておけば、git cloneしてdocker compose upするだけでデプロイできる筈です