本記事の目的
Docker・Rails初心者である私がDocker-compose
を使用しRails6
の環境構築を行った際
御見識ある方に情報を提供頂き、無事に環境構築する事ができました。
しかしながら、構築内容で実行している内容が初心者の私には非常に理解しづらく、
Rails5
での環境構築時と違い処理事項を間違えると環境構築できませんでした。
環境構築にあたり、ポイントが複数あると思います。
本記事では自身が相談し、回答頂いた事項を元に
- 環境構築時のコードを分解解説し自身の学習と知識定着を行う
- 自身の忘備録と今後の環境構築時のマニュアルとして使う
- 他の方に少しでも参考資料として役立ててもらえればとの意図
上記を目的にまとめました。
初心者ゆえに間違いがあるかもしれませんが、お気づきの点がありましたら、
ご指摘頂けますと幸いです。
少しでも参考になりましたら LGTM 頂けると感激です。
この記事で作成する環境
Docker-compose
- Web
- Ruby version:2.7〜
- Rails version:6.0.3.4〜
- db
- PostgreSQL version: 12.4〜
とりあえず使いたい方
早々に使ってみて構築したい方は下記GitHubリポジトリに公開しております。
READMEに手順記載しておりますが、下記のコマンドを順に実行して下さい。
- 下記サイトのREADMEより
git clone <Project name>
の実行 -
cd <Project name>
でプロジェクトディレクトリ移動 -
./qs setup
コマンド実行
*下記サイトのREADMEを参照して実行をお願いします。
Rails6-QS
コマンド完了まで20分ぐらいかかります。
辛抱強くお待ち願います。(笑)
終わるとブラウザでlocalhost:3000を叩けばお馴染みのRails初期画面が現れます。
DockerのDashboardにもプロジェクト名の中にweb,dbのコンテナがあるはずです。
この記事で作成するもの
- Dockerfile
- Docker-compose.yml
- シェルスクリプト(sample.bash)
*公開リポジトリはqs(クイックスタートの略)と名前をしております。 - .gitignore
- Gemfile
- database.yml
①Dockerfileの作成
まずはDockerfileから作成を行なっていきます。
Dockerfileの作成にあたっては
- 公式のサイト
Docker Quickstart - Docker Docs
Docker Docs
上記のサイトの説明がわかりやすく整備されています。
Dockerfile
で定義されたイメージを使って作成されるコンテナは、可能ならばエフェメラル(短命;ephemeral)にすべきです。私たちの「エフェメラル」とは、停止・破棄可能であり、明らかに最小のセットアップで構築して使えることを意味します。
上記サイト引用の通りDockerfile
は最小のセットアップで構築する事を目指しております。
以降で今回作成するDockerfile
を項目毎に説明していきます。
ファイルの構成
Rubyバージョンの指定 Version:2.7
FROM ruby:2.7
rails consoleで日本語を使う為に必要
ENV LANG C.UTF-8
警告非表示・リッスンポート設定
- DEBCONFの警告をなくすための設定
[Ubuntu manual]
http://manpages.ubuntu.com/manpages/bionic/man7/debconf.7.html
- apt-keyコマンドによって出る警告防止の為
- Dockerのマウント領域に生成されるファイルが、
キャッシュディレクトリの書き込み権限が無くてエラーを吐くケースがあリます。
XDG_CACHE_HOMEに書き込み権限のあるディレクトリを指定してますが、
どうもRailsのみの使用では発生しなさそうです。
問題なければ外してもいいと思います。 - コンテナが接続用にリッスンするポート
ENV DEBCONF_NOWARNINGS yes
ENV APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE yes
ENV XDG_CACHE_HOME /tmp
EXPOSE 3000
コンテナパッケージインストール
- apt-get とapt-get install-y でキャッシュ利用されず最新バージョンのパッケージを使用できる。
- ビルドツールのインストール
- PostgreSQL対応に必要です。
- vimのインストール*
- lessコマンド(linux)を追加する為に指定しています。*
- オープンソースのツールパッケージgraphvizを使う為*
*印部分は使わない場合は削除してもいいと思います。
RUN apt-get update -qq && apt-get install -y \
build-essential \
libpq-dev \
vim \
less \
graphviz
yarnのインストール
Rails6よりWebpackが標準となったので、yanrインストールがないとエラーが発生します。
- https対処の為
apt-transport-https
でパッケージをインストールしています。
- その後のコマンドはyarnのインストールです。
RUN apt-get install apt-transport-https
RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
RUN echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list
RUN apt-get update && apt-get install -y yarn
ワークディレクトリの作成
# setting work directory
RUN mkdir /app
WORKDIR /app
環境変数設定
- スクリプトやコンテナに入ったときに使う為
ENV HOME /app
Gemfile作成
COPY Gemfile /app/Gemfile
上記全てを記載すると下記の内容になると思います。
(*部分的にコメント追記しております。)
# use ruby version 2.7
FROM ruby:2.7
# using japanese on rails console
ENV LANG C.UTF-8
# remove warn
ENV DEBCONF_NOWARNINGS yes
ENV APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE yes
ENV XDG_CACHE_HOME /tmp
EXPOSE 3000
# install package to docker container
RUN apt-get update -qq && apt-get install -y \
build-essential \
libpq-dev \
vim \
less \
graphviz
# install yarn
RUN apt-get install apt-transport-https
RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
RUN echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list
RUN apt-get update && apt-get install -y yarn
# setting work directory
RUN mkdir /app
WORKDIR /app
# setting environment value
ENV HOME /app
# executing bundle install
COPY Gemfile /app/Gemfile
②Docker-compose.yml ファイルの作成
docker-composeのversion指定
現状は3です。
version: '3'
service作成
- image
データベース・webなどを指定します。
今回使われそうなdb
redis
chrome
web: &app
spring
solagraph
を指定しました。
必要に応じて削除等して修正頂ければと思います。 - port
ポートの設定(一般的に設定されるポート内容で作成しております。) - volume
マウント設定。db等Dockerコンテナ削除後に抹消しては困るデータの格納先の指定です。 - build
Dockerfile
等があるパスを示しますが、基本は.
でカレントディレクトリとなります。
補足的事項
-
shm_size
selenium-chromeを使用した場合メモリ不足なる事あるので少し増やしています。 -
.:
docker-composeの機能でボリュームの読み込みを高速化するために使用しています。 -
<<: *app
webサービスの設定をspringに継承しています。 -
tty: true
コンテナを起動し続ける設定 - ポート設定について
左側がホスト側のポートで、右側がコンテナ側のポートとなります。
これも上記全て記載してまとめると下記の通りとなります。
version: '3'
services:
db:
image: postgres
ports:
- "5432:5432"
volumes:
- data:/var/lib/postgresql/data:cached
environment:
POSTGRES_PASSWORD: postgres
redis:
image: redis
ports:
- "6379:6379"
command: redis-server --appendonly yes
volumes:
- redis-data:/data:cached
chrome:
image: selenium/standalone-chrome
ports:
- "4444:4444"
web: &app
build: .
command: bundle exec rails s -p 3000 -b '0.0.0.0'
volumes:
- .:/app:cached
- bundle:/usr/local/bundle:cached
- /app/.git
environment:
HOME: /app
RAILS_ENV: development
ports:
- "3000:3000"
tty: true
links:
- db
spring:
<<: *app
command: bundle exec spring server
ports: []
solargraph:
<<: *app
command: bundle exec solargraph socket --host=0.0.0.0 --port=7658
ports:
- "8091:7658"
links: []
volumes:
bundle:
driver: local
data:
driver: local
redis-data:
driver: local
③スクリプト作成
シェルスクリプトで実行内容をまとめておきます。こうする事によりDockerfile
を「停止・破棄可能であり、明らかに最小のセットアップで構築して使えること」が実現できます。シェルスクリプトとは、Unixコマンドを実行するために使用されるものです。
シェルスクリプトの基本抜粋
シャルスクリプトの解説記事ではありませんが、自身が対象シェルスクリプトを理解する為に基本事項を抜粋し記載します。
記事作成とシェル動作の理解にあたり下記サイトを参考にさせて頂きました。
[初心者向けシェルスクリプトの基本コマンドの紹介]
https://qiita.com/zayarwinttun/items/0dae4cb66d8f4bd2a337
[シェルスクリプトリファレンス]
https://shellscript.sunone.me/
-
宣言
#!/bin/bash
シェルスクリプトの宣言を行うために必要。
#!/bin/sh
の書き方もありますが、bash
の独自機能を使う場合はsh
ではNGとなります。
参考にしたサイト:https://sechiro.hatenablog.com/entry/20120806/1344267619
特にこだわりがなければ、#!/bin/bash とするといいと思われます。 -
コメントアウト
先頭に#をつけるだけです。 -
出力
echo
で出力 -
if条件
-
if
の基本の書き方はif [ 条件 ] then コマンド fi
です。 - 条件が真の場合
then
の次のコマンドを実行します。 - 違う条件の場合
elif [ 条件2 ]
を確認します。 - 真の条件がない場合
else
の次のコマンドを実行して終了します。 -
else
がない場合は、そのまま終了します。
-
-
引数の取り方
変数をアクセスする時変数名の前に$
を入れます。あるいは$
入れて変数を{}
で囲みます。 -
-e
特殊テキストをエスケープ -
$*
全部の引数をまとめて1つとして処理。同じような処理に$@
があります。
ダブルコーテーションで囲なければ$@と同じであるが、囲むと引数毎に出力するか、
まとめて一つの引数で出力するかの違いがあります。 -
$1
1番目の引数 -
$0
スクリプト名 -
関数
シェルスクリプトでは、関数を書いて引用することができます
Function () { echo }
-
終了処理
スクリプトを途中で終了するにはexit
コマンドに数字を与えます。 -
Swith
switchの基本の書き方はcase 変数 in 条件・値) コマンド ;; esac
です。
条件・値が変数と合う場合次のコマンドを実行します。
シェルスクリプト構成の概要
- 最初にdocker-composeのコマンドをフルパスでdcに指定します。
- dockerがインストールしていない場合はインストールをするようにメッセージが出ます。
- それぞれの変数に値を格納します(rm, app, db, app_name)
- echoで$1のコマンド値を実行
- $1は関数で与えられた値(関数によってはswitch構文で展開)される与えられたコマンドを処理する形になります。
- $0でスクリプト名を記載しhelpとして使用できるようにしています。
上記内容で、各種コマンドや設定事項を入れ込んだスクリプトファイルを作成すると下記の通りとなります。
dc=$(which docker-compose) # docker-compose command with full path
if [[ -x "$dc" ]]; then
:
else
echo "Please install Docker before run this command."
exit 2
fi
rm="--rm" # To destroy a container
app="web" # describe $application service name from docker-compose.yml
db="db" # describe database service name from docker-compose.yml
app_name=$(pwd | awk -F "/" '{ print $NF }') # get project dir name
# define container name
app_container="${app_name}_${app}_1"
db_container="${app_name}_${db}_1"
echoing() {
echo "========================================================"
echo "$1"
echo "========================================================"
}
rm_pids() {
if [ -f "tmp/pids/server.pid" ]; then
rm -f tmp/pids/server.pid
fi
}
create_project() {
echoing "Exec Bundle Install for executing rails new command"
compose_build $app
bundle_cmd install
echoing "Exec rails new with postgresql and webpack"
bundle_exec rails new . -f -d=postgresql $*
echoing "Update config/database.yml"
mv database.yml config/database.yml
echoing "Exec db create"
bundle_exec rails db:create
echoing "docker-compose up"
compose_up $app
echo "You can access to localhost:3000"
}
init_services() {
echoing "Building containers"
$dc down -v
$dc build --no-cache $app
bundle_cmd install
if [ "--webpack" == "$1" ]; then
run_yarn install
fi
rails_cmd db:migrate:reset
rails_cmd db:seed
rm_pids
$dc up $app
}
compose_up() {
echoing "Create and start containers $*"
rm_pids
$dc up -d "$1"
}
compose_down() {
echoing "Stop and remove containers $*"
$dc down $*
}
compose_build() {
echoing "Build containers $*"
$dc build $*
}
compose_start() {
echoing "Start services $*"
rm_pids
$dc start $*
}
compose_stop() {
echoing "Stop services $*"
$dc stop $*
}
compose_restart() {
echoing "Restart services $*"
$dc restart $*
}
compose_ps() {
echoing "Showing running containers"
$dc ps
}
logs() {
echoing "Logs $*"
$dc logs -f $1
}
invoke_bash() {
$dc run $rm -u root $1 bash
}
invoke_run() {
renv=""
if [ -n "$RAILS_ENV" ]; then
renv="-e RAILS_ENV=$RAILS_ENV "
fi
if [ -n "$TRUNCATE_LOGS" ]; then
renv="$renv -e TRUNCATE_LOGS=$TRUNCATE_LOGS "
fi
dbenv=""
if [ -n "$DISABLE_DATABASE_ENVIRONMENT_CHECK" ]; then
dbenv="-e DISABLE_DATABASE_ENVIRONMENT_CHECK=$DISABLE_DATABASE_ENVIRONMENT_CHECK "
fi
$dc run $rm ${renv}${dbenv}$*
}
run_app() {
invoke_run $app $*
}
run_db() {
invoke_run $db $*
}
run_spring() {
$dc exec spring $*
}
run_solargraph() {
invoke_run solargraph $*
}
rails_server() {
rm_pids
# $dc run $rm ${renv}--service-ports $app rails s -p 3000 -b 0.0.0.0
$dc run $rm --service-ports $app bundle exec foreman start -f Procfile.dev
}
rails_db() {
case "$1" in
set)
rails_cmd db:migrate
;;
up)
rails_cmd db:migrate:up VERSION="$2"
;;
down)
rails_cmd db:migrate:down VERSION="$2"
;;
reset)
rails_cmd db:reset
;;
*)
rails_cmd db:migrate:status
;;
esac
}
spring_db() {
case "$1" in
set)
spring_cmd rake db:migrate
;;
up)
spring_cmd rake db:migrate:up VERSION="$2"
;;
down)
spring_cmd rake db:migrate:down VERSION="$2"
;;
reset)
spring_cmd rake db:reset
;;
*)
spring_cmd rake db:migrate:status
;;
esac
}
spring_dive() {
$dc exec spring bash
}
rails_cmd() {
bundle_exec rails $*
}
rake_cmd() {
bundle_exec rake $*
}
rspec_cmd() {
$dc start chrome
bundle_exec rspec $*
}
test_cmd() {
bundle_exec test $*
}
bundle_cmd() {
run_app bundle $*
}
bundle_exec() {
run_app bundle exec $*
}
rubocop_cmd() {
bundle_exec rubocop $*
}
rails_console() {
bundle_exec rails c $*
}
spring_cmd() {
run_spring spring $*
}
solargraph_cmd() {
run_solargraph solargraph $*
}
rake_reset_db() {
echoing "Running reset db"
compose_stop $app
DISABLE_DATABASE_ENVIRONMENT_CHECK=1 rake_cmd "db:reset"
rake_cmd "db:fdw:setup"
RAILS_ENV=test rake_cmd "db:fdw:setup"
compose_up $app
}
db_console() {
# from config/database.yml
database="development"
username="postgres"
port="5432"
run_db psql -h $db_container -p $port -U $username $database
}
db_dump() {
# from config/database.yml
database="development"
username="postgres"
port="5432"
tm=$(date +\%Y\%m\%d-\%H\%M)
dump_file=tmp/dbdump-${dbname}-${tm}.dump
echoing "Dump database $dbname data to $dump_file"
run_db pg_dump -h $db_container -p $port -U $username --disable-triggers $database >$dump_file
echo "done"
}
run_yarn() {
run_app bin/yarn $*
}
run_npm() {
run_app npm $*
}
run_webpack() {
run_app webpack $*
}
cmd=$1
shift
case "$cmd" in
setup)
create_project $* && exit 0
;;
init)
init_services $* && exit 0
;;
ps)
compose_ps && exit 0
;;
up)
compose_up $* && compose_ps && exit 0
;;
build)
compose_build $* && exit 0
;;
start)
compose_start $* && exit 0
;;
stop)
compose_stop $* && exit 0
;;
restart)
compose_restart $* && exit 0
;;
down)
compose_down $* && exit 0
;;
logs)
logs $*
;;
bash)
invoke_bash $*
;;
run)
invoke_run $*
;;
server)
rails_server $*
;;
rails)
rails_cmd $*
;;
db)
rails_db $*
;;
cons)
rails_console $*
;;
rake)
rake_cmd $*
;;
rspec)
rspec_cmd $*
;;
test)
test_cmd $*
;;
bundle)
bundle_cmd $*
;;
rubocop)
rubocop_cmd $*
;;
reset-db)
rake_reset_db
;;
psql)
db_console $*
;;
db-dump)
db_dump $*
;;
yarn)
run_yarn $*
;;
npm)
run_npm $*
;;
webpack)
run_webpack $*
;;
spring)
spring_cmd $*
;;
sdb)
spring_db $*
;;
sdive)
spring_dive $*
;;
solargraph)
solargraph_cmd $*
;;
*)
read -d '' help <<-EOF
Usage: $0 command
Service:
setup Create new rails application
init Initialize backend services then run
ps Show status of services
up Create service containers and start backend services
down Stop backend services and remove service containers
start Start services
stop Stop services
logs [options] default: none. View output from containers
bash [service] invoke bash
run [service] [command] run command in given container
App:
server Run rails server
rails [args] Run rails command in application container
rake [args] Run rake command in application container
db [args] Run rails db command you can use set(migrate), up, down, reset, other is status
ex: ./qs db set #running rails db:migrate
./qs db up 2019010101 #running rails db:migrate:up VERSION=2019010101
rspec [args] Run rspec command in application container
test [args] Run Minitest command in application container
bundle [args] Run bundle command in application container
cons Run rails console
rubocop [args] Run rubocop
yarn Run yarn command in application container
npm Run npm command in application container
webpack Run webpack command in application container
Spring
spring Exec spring command in Spring container
sdive Into spring container
sdb [args] Run rails db command you can use set(migrate), up, down, reset, other is status
ex: ./qs db set #running rails db:migrate
./qs db up 2019010101 #running rails db:migrate:up VERSION=2019010101
Solargraph
solargraph Run solargraph command in Spring container
DB:
reset-db reset database in DB container
psql launch psql console in DB container
pg-dump dump database data as sql file in DB container
EOF
echo "$help"
exit 2
;;
esac
その他作成ファイル
その他の設定ファイルですが、一般的な構築で作成しておりますので説明は割愛致します。
補足的な事項として、DBとしてPostgreSQLを選んでおります。
理由としてMySQLではRails6.0よりバリデータのエラーが発生する事があり設定を追加する必要がある為です。
この点に関してはMySQLを使用する場合は設定を行えばいいのですが、
PostgreSQLでは設定不要の為、こちらをDBとして採用しています。
詳細は下記サイトを参照ください。
Rails 6.0で"Uniqueness validator will no longer enforce case sensitive comparison in Rails 6.1."という警告が出たときの対処法
default: &default
adapter: postgresql
encoding: utf8
min_messages: WARNING
host: db
port: 5432
username: postgres
password: postgres
pool: 5
timeout: 5000
stats_execution_limit: 10
development:
<<: *default
database: development
test:
<<: *default
database: test
production:
<<: *default
database: production
Gemfile
source 'https://rubygems.org'
gem 'rails', '~> 6.0'
.gitignore
.bash_history
.bundle
.solargraph
.atom
.vscode
.viminfo
あとがき
ローカルでのRails6の開発環境構築はhomebrewでrbenvなどを使いながら
それほど大きな工数がかからなかった印象ですが、安定的なコンテナでの開発環境の構築は、
Docker公式サイトでもRails5での記載で、Rails6での構築は難しい印象でした。
また公式サイトのQuickStartでは実行コマンドがDockerfileに記載されており、
出来る限り最小のセットアップで記載する場合、シェルスクリプトへ分けた方が
開発環境としてメンテナンス性も含めいいと考えられます。
*本番環境はDockerfileが変わる可能性も含め
この記事を読了頂きありがとうございました。何らか開発の助けになれば幸いです。
最後に、この記事をまとめるにあたり多くの協力とご助言を賜りました。
ありがとうございました。
参照サイト・資料元
Quickstart Compose and Rails
Dockerfileのベストプラクティス
Bashスクリプト実行サンプル
新しいLinuxの教科書 SB Creative