フルスタックエンジニアへの道 part 1  ~Rails 7 × Dockerの環境構築 1

# はじめに

ゼロから100まで、すべて自分の力でウェブアプリケーションを作り上げることのできる技術と経験を得ることで、プロジェクト・マネジメントができるリーダーになりたい…フロントエンド、バックエンド、デザイン、インフラなど関係なくなんでもできる最強のエンジニアになりたい…Modern-day Da Vinciになりたい…(笑)。

この目標を達成するための方法を考えてみた結果、**現場で行われている開発プロセスを自分ひとりでやってみるのが一番速いんじゃないか**という仮説を立てた。

そこで、私個人の成長記録として、この「フルスタックエンジニアへの道」という連載をやっていこうと思う。

読者諸兄におかれましては、この記事で技術的な知識を得るというよりも、私が**どんなことをどんな順番で勉強したのかを参考にしていただいたり、シンプルに頑張ってるヤツ(私^^;)を見て仕事や勉強へのモチベーションとしていただければ**なと思います。

### なぜそう思った?

エンジニア歴5ヶ月目の私は、現在Rails 6を使った社内アプリケーションの開発をやっている。(で、明後日デプロイする笑)。この現場で経験・学習できるのは、もちろんRailsがメイン。あとはちょっとSQLの知識もつくかなというくらい。

インフラまわりは先輩にやってもらっているし、Dockerのイメージを取り込めば(使っているPCがM1 Macという点を除けば)特に環境を意識しなくてもいい。

デザインはもともとウェブマーケティングをやっていたこともあってそこそこできるけれど、ゼロからデザインを作ったこともなければ、UIを作ったこともない。

つまり、、、

**現場で場当たり的に勉強してるようじゃ、いつまでたってもフルスタックエンジニアになれない!!!!**

と、エンジニアを5ヶ月間やって痛感した。もちろん、この5ヶ月間はRailsを使った開発でめちゃくちゃ勉強にはなったけれど、さらに爆速で知識をつけていくには個人開発がベストなんじゃないかと。

### 個人開発のメリット・デメリット

メリットは、普段はやらないインフラにも手を出せる、自分の知識が整理できるまでとことん理解し、いじくり回せるというところ。やっぱり現場だと「納期」という死神()に睨まれているので、

自分が理解すること <<<< プログラムが動くこと

という不等号が覆せない。自分が仕事でやらないことにチャレンジする点にこそ、個人開発の醍醐味がある。(ま、まだやり始めたとこなんで「知らんけど(責任放棄)」状態ではあるが笑)

デメリットは、レビューをもらえないので、「良いプログラム」にしていくのは難しいというところ。ここはRubocopとか入れてある程度リファクタできるにしても限界がある。したがって、もし目的が「スマートなコードを書けるようになる」なのであれば、個人開発は方法として適切ではないと思う。先輩にガンガンレビューもらって、どうぞ。

### 縛り内容

上記のようなメリットを最大限活かすなら、やっぱり縛りは重要。今回の記事ではRails 7とDockerを使ってウェブアプリケーションの骨格を作るところまでやってみるが、それは、「現場でRails 7をまだ使っていない」「Dockerの環境構築を自分の手でやっていない」という理由から。もしこれを読んで自分もやってみようと思われたのであれば、**仕事でやらないこと、使っていない技術を使うという縛りにする**と良いじゃないかと思う。

縛り内容は以下のとおり。

- アジャイル開発
- Dockerを使った開発環境管理
- Railsでのウェブアプリケーション開発
- AWS(EC2、RDS)
- Githubでのバージョン管理
- adobe XDでのUX/UI設計

これらの条件を満たしながら開発を行っていけば、自ずと最強になれるのでは?という期待のもと、趣味としてwebアプリケーションを開発していく。また、上記だけでなく、

- LINEなどと連携するAPIを使ってみる
- ネイティブアプリっぽく見せるためのJavascriptやvue.js(SPA)を入れてみる
- アレクサとか音声UIにも手を出したい

などなど、、、現場で部分的に導入されている技術や単純にかっこいいと思う技術も取り入れていきたいなと考えている。まあ、考えているだけでできるかどうかは知らない。「とりあえずやってみよう」が大事!!また、スケジュールなどは決めないことにする。スケジュールを決めるとどうしても仕事っぽくなってしまって楽しくなくなってしまうので、気が赴くまま、徒然に、自分の知的好奇心に任せたアジャイルでいこう。

### **最初のお題は、「メモ帳」**

環境開発から入って、知識ゼロでもコードだけなら誰でも書けそうなメモ帳をとりあえず作って、そこからどんどんアプリを進化させていこうかなーと。大事なので何度も言うが、個人開発でやりたいのはRailsでアプリケーションを作ることではなくて、「**使ったことのない技術を使うこと**」なので、逆にプログラムの本体はサルでも書けそうなものにしておく(最初は)。

まあ、そうは言いつつ最終ゴールは近未来っぽい(SF映画に出てきそうな)イカしたタスク管理ツールにしていきたい。『7つの習慣』や『大富豪の仕事術』(ダイレクト出版)を実践していくためのサポートツールという位置づけ。

(ぶっちゃけると最近見たアイアンマンのトニー・スタークがかっこよすぎて、なんかかっこいいものを作りたいだけというのはここだけの話)

アプリ名は、とりあえず「sloth」に決めた。本質的に怠惰な自分を動かすためのアプリ。

では、長くなったけれど今回のお題に入る…前にまだちょっと経歴とか前提を紹介しておく。

# 経歴など:

- 29歳の男(公立大学の経済学部卒。統計とかエクセルはぼちぼちいじっていた。)
- 6年間セールス、マーケティング業に従事。後半2年ほどは事業部リーダーとして商品開発から販売、経営までこなす。
- (思い切って)エンジニアにジョブチェンジ。それまで全くプログラミングをやったことがなく、Railsでの開発歴5ヶ月のぺ ーペー。

# 前提:

M1 MacbookPro

MacOS Monterey 12.3.1

docker desktopをインストール済み

# 今回のゴール: dockerからrails new

ここから退屈なパートになります。よほど興味が無い限りサラッと読み飛ばしちゃってください。

# Dockerの環境構築

そもそも、なぜdockerを使うのか?

というところから出発してみる。なぜ使うのか?を考えるなら、使わなかったときに何がダルいのかを考えるのがよさげ。

ソロの場合: プロダクトごとに環境構築するのダルい。

チームの場合: それぞれのローカル環境に引きずられながら環境構築するのダルい。

→つまり、環境構築が超めんどくさいので、かんたんにしちゃおうという話。

Dockerのド基礎もわかってなかったので、とりあえずそのあたりの知識を補充。

参考: dockerの基本的な用語↓

[https://qiita.com/ok2/items/ee18407e01a125fb4555](https://qiita.com/ok2/items/ee18407e01a125fb4555)

### Dockerでやりたいことの概要

ここを↓めちゃくちゃ参考にさせていただきました。

[https://tmasuyama1114.com/docker-compose-rails6-mysql-development/](https://tmasuyama1114.com/docker-compose-rails6-mysql-development/)

- Dockerfileに作成したいDockerイメージを記述する
- docker-compose.ymlに、Dockerイメージをビルドする際に起動させる(複数の)コンテナの情報を記述する
- コンテナが立ち上がったらrails new

補遺:
Linuxコマンド初心者だったのでprogateの「Command Line 基礎編」やってみた。10分で基本的なコマンドが使えるようになったのでやって損はないと思う。あとCUI触れるのってかっこいい。厨二病。

### 開発用のディレクトリを作成

```bash
mkdir sloth-web # sloth-webディレクトリを作成
cd sloth-web/ # 今作ったディレクトリに移動
```

### dockerの環境構築に必要なファイルを用意

- Dockerfile ーdockerを実行するためのファイル
- docker-compose.yml ーどういうイメージを作るのか決めるファイル
- Gemfile ーRailsで使うGemをまとめたファイル
- Gemfile.lock ーGemfileをもとにインストールされた(細かい)Gemの一覧とバージョンが記載
- entrypoint.sh ーdocker初回起動時に実行したい処理を書いたスクリプト

```bash
# touch ファイル名 で、ファイルを作成できる。中括弧で囲めばまとめて作成できる。
touch {Dockerfile,docker-compose.yml,Gemfile,Gemfile.lock,entrypoint.sh}
# ファイル名, ←ここに空白があるとエラーになるので注意。ファイル名とカンマは詰めて書くこと
```

```bash
ls # lsでカレントディレクトリのファイルを表示させることができる。きちんとできているか確認のため
→
Dockerfile		Gemfile.lock		entrypoint.sh
Gemfile			docker-compose.yml
```

### Dockerfileの編集

↓完成形。見なくても大丈夫です(笑)

```bash
# syntax=docker/dockerfile:1
#
# Docker Multi-Stage Builds
# [development]
#
FROM ruby:3.1.1-slim-bullseye AS build
ENV APP_ROOT /sloth-web
ENV LANG=C.UTF-8
ENV TZ=Asia/Tokyo

RUN set -eux && \
    apt-get update && \
    apt-get install -y --no-install-recommends \
      build-essential \
      default-mysql-client \
      default-libmysqlclient-dev \
    && \
    gem install bundler:2.3.7 && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/*

RUN mkdir ${APP_ROOT}
COPY Gemfile Gemfile.lock ${APP_ROOT}/

# ruby-debug-ide OR debase のインストールでこける場合は[jobs=1]とする
WORKDIR ${APP_ROOT}
RUN bundle install --jobs=4

#
# Execution container
#
FROM ruby:3.1.1-slim-bullseye
ENV APP_ROOT /sloth-web
ENV LANG=C.UTF-8
ENV TZ=Asia/Tokyo

# USER rails

RUN set -eux && \
    apt-get update && \
    apt-get install -y --no-install-recommends \
      default-libmysqlclient-dev  \
      imagemagick \
      nodejs \
      vim \
      locales \
      git \
    && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/*

WORKDIR ${APP_ROOT}
COPY --from=build /usr/local/bundle /usr/local/bundle

COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]

EXPOSE 3002

```

これはあるところからパクってきたDockerfileだが、「はい暗号キター」って感じ。マジでなんもわからん。が、「**なんもわからん→言ってることはわかる」まで気が済むまでできるのが個人開発のいいところ**。それぞれ詳細に見ていく。

```bash
FROM ruby:3.1.1-slim-bullseye AS build
```

`ruby:3.1.1` まではわかる…。嘘つきました。FROMってなんや。

↓Dockerfileリファレンスに書いてた。

[https://docs.docker.jp/engine/reference/builder.html#from](https://docs.docker.jp/engine/reference/builder.html#from)

どうも、親イメージを指定するらしい。今回だと、「ruby使うよ~」って言ってあげるってことか。試しに動かしてみる。

```bash
$ docker container run -it ruby:3.1.1-slim-bullseye /bin/bash

Unable to find image 'ruby:3.1.1-slim-bullseye' locally
3.1.1-slim-bullseye: Pulling from library/ruby
2203022c5aa9: Pull complete 
0e6f29ca1620: Pull complete 
9987e0079ca5: Pull complete 
90c75612634b: Pull complete 
a6c7a1fc9bfc: Pull complete 
Digest: sha256:b7e41f3eb3205ffffd3f1cbce6e6d6ea939c0e19aaadad3c985197e08e748029
Status: Downloaded newer image for ruby:3.1.1-slim-bullseye

# dockerに入れた
$ ls # とりあえず実行できそうなコマンド打ってみる
bin  boot  dev  etc  home  lib  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var

# 動いたー!!
```

ってことで、、、FROM ~~以下は`ruby:3.1.1-slim-bullseye`  を使ってなんかいろいろ実行していくわけね…。 ASは、名前をつけて後ろで参照できるようにしているらしい。

次。

```bash
-slim-bullseye
```

↓ここがわかりやすかった

[https://prograshi.com/platform/docker/docker-image-tags-difference/](https://prograshi.com/platform/docker/docker-image-tags-difference/)

つまり、Docker hubが提供しているイメージの種類ってことか。で、slimなんちゃらは軽いと。

```bash
ENV APP_ROOT /sloth-web
ENV LANG=C.UTF-8
ENV TZ=Asia/Tokyo
```

ま、なんとなくわかる。ルートディレクトリはここだよ~、言語はこれだよ~、タイムゾーンはここだよ~って環境変数で定義してあげる。

```bash
RUN set -eux && \
    apt-get update && \
    apt-get install -y --no-install-recommends \
      build-essential \
      default-mysql-client \
      default-libmysqlclient-dev \
    && \
    gem install bundler:2.3.7 && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/*
```

最初の山場。RUNはまあ命令実行するんでしょってのがわかる。

`&&` はコマンドを続けて実行、`\`は次の行に命令文を書くときに使う。

以下、それぞれの命令について(全部Dockerfileの設定云々ではなく、シェルコマンド)

```bash
set -eux
```

参考: [https://atmarkit.itmedia.co.jp/ait/articles/1805/10/news023.html](https://atmarkit.itmedia.co.jp/ait/articles/1805/10/news023.html)

set: シェルの設定を確認・変更するコマンド。e,u,xはそれぞれオプションで、、、

-e: パイプやサブシェルで実行したコマンドが1つでもエラーになったら直ちにシェルを終了する

-u: パラメーター展開中に、設定していない変数があったらエラーとする

-x: トレース情報として、シェルが実行したコマンドとその引数を出力する。情報の先頭にはシェル変数PS4の値を使用

```bash
apt-get update &&
```

参考:

[https://webkaru.net/linux/apt-get-command/](https://webkaru.net/linux/apt-get-command/)

apt-get: Debian系のディストリビューション（DebianやUbuntu）のパッケージ管理システムであるAPT（Advanced Package Tool）ライブラリを利用してパッケージを操作・管理するコマンド

update: APTライブラリのインデックスを更新します。更新先は/etc/apt/sources.listに記述されています。

```bash
apt-get install -y --no-install-recommends \
	build-essential \
	default-mysql-client \
	default-libmysqlclient-dev \
```

install: 後ろで指定したものをインスコ(死語)

-y: インタラクティブ（ユーザーへの問い合わせ）に「yes」と答えます。

—no-install-recommends: デフォルトだと recommends しているだけの必須ではないパッケージも一緒に入って時間がかかるので `--no-install-recommends` をつける

build-essential: [https://qiita.com/h_tyokinuhata/items/431a56fb054145468c16](https://qiita.com/h_tyokinuhata/items/431a56fb054145468c16)

default-mysql-client: まんま

default-libmysqlclient-dev: mysql使うのにいるらしい。

あとは雰囲気で察することができる。

バーっと調べてみて、この暗号の羅列が何をしているのかだいたいわかってきた。

↓

シェルの設定して、ごちゃごちゃパッケージインストールしている(超訳)

サクサクいこう。

```bash
RUN mkdir ${APP_ROOT}
COPY Gemfile Gemfile.lock ${APP_ROOT}/
```

ルートディレクトリつくって、2つのファイルをルート直下にコピー。

```bash
WORKDIR ${APP_ROOT}
RUN bundle install --jobs=4
```

ワーキングディレクトリをルートにして、`bundle install`を走らせる。jobsは並列で何個ジョブを走らせるか。

```bash
# もっかい親イメージを立て直す
FROM ruby:3.1.1-slim-bullseye 
ENV APP_ROOT /sloth-web 
ENV LANG=C.UTF-8
ENV TZ=Asia/Tokyo
```

なぜ複数FROMを書いて処理をわけるのかについては、こちらを参照されたい。

[https://qiita.com/minamijoyo/items/711704e85b45ff5d6405](https://qiita.com/minamijoyo/items/711704e85b45ff5d6405)

今回のDockerfileでは

```bash
apt-get clean && \
    rm -rf /var/lib/apt/lists/*
```

こんな感じでごみ処理をしているので、余計なファイルを残さないようにできる(=イメージサイズを小さくできる)。この辺の話はDocker imageのレイヤー構造という概念を理解する必要がありそうだが、尺が長くなってきたので勉強はまた今度暇なときに。(そもそもmulti stage buildが無い時代を知らないので、まだありがたみがわかってない…)

ここはほぼ同じような考え方なので割愛。いろいろインストールして、bundleディレクトリにつっこむ!!

```bash
RUN set -eux && \
    apt-get update && \
    apt-get install -y --no-install-recommends \
      default-libmysqlclient-dev  \
      imagemagick \
      nodejs \
      vim \
      locales \
      git \
    && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/*

WORKDIR ${APP_ROOT}
COPY --from=build /usr/local/bundle /usr/local/bundle
```

では最後…。

```bash
COPY entrypoint.sh /usr/bin/ # さっきつくったやつをdockerにコピる
RUN chmod +x /usr/bin/entrypoint.sh # 実行できるよう権限を変更
ENTRYPOINT ["entrypoint.sh"] # コンテナ実行時にこれを実行

EXPOSE 3002 # ネットワーク絡みの設定だが、、、よくわからん
```

EXPOSEについて:

[https://zenn.dev/suiudou/articles/5e1dfd1008bf29](https://zenn.dev/suiudou/articles/5e1dfd1008bf29)



### entrypoint.shを編集

```bash
#!/bin/bash
set -e # エラーが起きたら即終了
rm -f /sloth-web/tmp/pids/server.pid # server.pidファイルが存在するときにサーバーが再起動しないようにする。Rails特有らしい。
exec "$@" # コンテナのメインプロセスを実行
```

ここはおまじないとしてすっ飛ばしてokかな。

### Gemfile

```bash
# frozen_string_literal: true

source "https://rubygems.org"
git_source(:github) { |repo| "https://github.com/#{repo}.git" }

ruby "3.1.1"

# Bundle edge Rails instead: gem "rails", github: "rails/rails", branch: "main"
gem "rails", "~> 7.0.2.2"
```

Rails触ってたら死ぬほど見るので割愛。

### docker-compose.yml

ここも書いてたら長くなるので記事を分けますね。ひとまずコードだけ。

```bash
# docker-compose
#
# Development environment: How to create a network.
# ```
#    docker network create shared-network
# ```
#
version: "3.9"
services:
  web-sloth:
    build:
      dockerfile: ./Dockerfile
      context: .
    command: tail -f /dev/null
    volumes:
      - .:/sloth-web:delegated
    environment:
      BUNDLE_APP_CONFIG: /usr/local/bundle
      TZ: "Asia/Tokyo"
    hostname: web-sloth
    ports:
      - "3002:3002"
    depends_on:
      - redis-sloth
    networks:
      - shared-network
  redis-sloth:
    image: redis:6.2-alpine
    hostname: redis-sloth
    command:
      - --save ""
      - --stop-writes-on-bgsave-error no
    ports:
      - "6382:6379"
    networks:
      - shared-network
#  db-sloth: あとでやる

networks:
  shared-network:
    external: true
```

ひとまず、1)本体と、2)session用にredisと、3)mysqlのDBをそれぞれコンテナ作っておくって感じ。

# docker compose

```bash
# docker composeをRuby Mine上で実行させまして...
# コンテナを確認
$ docker ps
CONTAINER ID   IMAGE                             COMMAND                  CREATED          STATUS          PORTS                                            NAMES
f29b7f06492c   sloth-web_web-sloth               "entrypoint.sh tail …"   26 seconds ago   Up 25 seconds   0.0.0.0:3002->3002/tcp                           sloth-web_web-sloth_1
36aa450794c5   redis:6.2-alpine                  "docker-entrypoint.s…"   26 seconds ago   Up 25 seconds   0.0.0.0:6382->6379/tcp                           sloth-web_redis-sloth_1
```

コンテナ上がってるー!!!

### rails new

```bash
# まずdocker入りまして...
$ docker exec -it f29b7f06492c bash 

# rails new(mysqlの指定と、今回jsを結構使いたいので邪魔なturbolinksをkill)
$ rails _7.0.2.2_ new . -d mysql --skip-turbolinks

# mysqlが入らんと言われたので、もう一度ビルドしてdockerに入り直して...
$ bundle install

```

で、rails sすると、、、

![スクリーンショット 2022-05-24 1.50.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2496677/4ff4fc0d-334d-74e3-1ad2-b09c8ba9869b.png)


サーバー定義してないので、まだいつもの画面を見ることができないのだ…

(次はdocker-compose.ymlの詳しいやつと、DB設定編を書こうと思います。)

## P.S. 「面白いヤツだな…」と思ってくれた方、ぜひLGTMお願いします

人間とは単純なもので…誰かに評価してもらったり、応援してもらったりするだけでめちゃくちゃやる気がでるものです。(記事を書くのもそうだし、開発それ自体もめっちゃやる気になる)

よかったら、お願いします...!