Help us understand the problem. What is going on with this article?

herokuで本番環境までを構築する上で知っておきたいこと

More than 1 year has passed since last update.

概要

heroku上で本番までの一連の環境を用意する機会に恵まれたので、その際に得られた情報を掲載しておきたいと思います。

本番環境までを通してheroku上で構築するケースは、スタートアッププロジェクトが多いと思います。
その後、サービス利用者が増えてきて構成を見直すタイミングになったら、GCPやAWSを考えるという感じです。

今回一通り構築してみて、herokuで得られる恩恵は結構多いと思いました。
一部制限などもあって、大規模サービス向きではないとも思いますが、スタートアップから中規模であれば十分な機能が用意されています。

場合によっては大規模でも、マイクロサービス化などで十分対応できそうです。

以下で本番環境までの一通りの環境を構築する上で、知っておくと良さそうなことを述べていきます。
Heroku Private Spacesは使用していないため、そちらの情報はありません。

また、以下の内容には間違いが含まれている可能性があります。
間違いがある場合は、指摘をお願いします。

herokuのアプリケーション動作の環境変数は常にproductionにする

herokuでは、heroku上で動作するアプリケーションは、"本番環境とできるだけ同じにしておくべき"、という考え方があるようです。

なので、Railsであれば、RAILS_ENVとRACK_ENVはproductionを常に設定しておきます。
NodeJSを使うのであれば、NODE_ENVをproductionにしておきます。
設定を省略すると、デフォルトのproductionが適用されます。

もちろん、環境ごとに動作を分けたいケースや、使用する外部サービスの接続先を切り分けたい、などの場面は当然存在するので、そういった場合には、サーバの環境変数を使って切り分けを行います。

例えばS3のKEYとSECRETをstagingの環境変数に設定しておいて、それを使うようにします。

heroku config:set S3_KEY=XXX --remote staging
heroku config:set S3_SECRET=YYY --remote staging

全部の環境をproductionで動作させるのが難しい場合でも、stagingとproductionの環境は、RAILS_ENV=productionで動作させておくことをお勧めします。

参考:Managing Multiple Environments for an App

herokuのdynoは1日1回再起動される

herokuのdynoは1日1回自動的に再起動されます。
タイミングは常に同一という訳ではありませんが、大体24時間ごとに再起動されます。

そのため、本番環境では動作させるdynoを、Standard 1x以上のプランにして、dynoの数を2つ以上にしておく必要があります。
dynoが1つの場合は、ダウンタイムが発生してしまいます。

dynoが2つ以上の場合は、同時に再起動されないよう、dynoマネージャが適切に再起動の間隔を開けてくれます。

staging環境まではdynoは1つで良いかもしれませんが、staging環境で本番前にレスポンスまできちんと確認する必要があるなら、stagingもproductionと同等構成にした方が良いかもしれません。

参考:Dynos and the Dyno Manager: restarting

他のタイミングでの再起動

また、herokuのdynoは他のタイミングでも再起動されます。

  • 新しいコードをリリースした時
  • 環境変数を変更した時
  • アドオンを変更した時
  • heroku restart を実行した時

heroku restartで再起動するのは当然ですが、環境変数やアドオン変更でも再起動するのは意外かもしれません。

heroku restartコマンドはgracefulに実行される

1日1回の自動的な再起動も、heroku restartによる再起動も、プロセスに対してSIGTERMが発行されます。
SIGTERMを受け取ったプロセスは、正常に処理を終了してEXITします。

SIGTERMの発行は、ログ上でも確認できます。

Stopping all processes with SIGTERM 

ただし、dynoマネージャは SIGTERMの発行後30秒経過してプロセスが残っている場合、SIGKILLを発行して強制的に終了させるので、場合によっては注意が必要です。

参考:Dynos and the Dyno Manager: shutdown

Prebootを使ってデプロイ時のダウンタイムをゼロにする

herokuにはPrebootという機能があり、これを使うと、デプロイ時のダウンタイムをゼロにすることができます。
しかし、この機能には注意が必要です。

Prebootでは、新しいバージョンのアプリがデプロイされたDynoを起動し、古いDynoと5分程度共存させます。
この間、二つのバージョンのアプリが共存する状態となります。
新しいDynoの起動から、約5分経過した後に、古いDynoへのルーティングは停止され、全てのリクエストが新しいDynoへルーティングされます。

Prebootの設定は以下のコマンドで確認できます。
デフォルトでは無効です。

heroku features -a sushi

有効化/無効化は以下のコマンドで可能です。

# 有効化
heroku features:enable preboot -a sushi
# 無効化
heroku features:disable preboot -a sushi

Prebootは、新しいバージョンで、データベースのデータの処理を大きく変えた場合や、データの意味を変更した場合には使わない方が無難です。
また、大きくプログラムを変更したのであれば、ダウンタイムをゼロにするよりも、充分なメンテナンス期間と確認時間を取って、アプリケーションを再開させる方が適切です。

しかし、軽微な変更が多い場合には、ダウンタイムを気にせずにすむのは、開発者としてはありがたいでしょう。

参考: Preboot

heroku上には画像ファイルなどをアップロードしておけない

heroku上には、gitに含まれない画像ファイルなどをアップロードしておくことができません。
アップロード自体はできますが、再起動時にファイルは全部削除されてしまいます。

そのため、画像ファイルなどをアップロードするようなアプリケーションを組む際には、Amazon S3などの外部ストレージに保存しておく必要があります。

Railsであれば、carrierwavefog-awsなどで、S3へアップロードされるように組む必要があります。

参考: Dynos and the Dyno Manager: ephemeral-filesystem

使用するミドルウェアは公式の推奨を使うようにする

herokuで使用するミドルウェアは、公式の推奨がある場合、できるだけ推奨を使った方が有利です。

herokuの推奨データベースは、postgresですが、postgresを利用した場合いくつかの恩恵が受けられます。

例えば

などです。

postgres関連のコマンドきちんと用意されているので、production環境からstaging環境へのデータコピースクリプトを組む際にも、他のアドオンよりも楽に設定ができます。

Slug(スラグ)が大きくならないようパッケージ化しない

Railsアプリでは、bundle packageによって、アプリケーションのパッケージ化ができますが、gemを含んだアプリケーションのパッケージ化は行わない方が無難です。

herokuには

  • スラグは500MBまで
  • コンパイル時間は15分まで

という制限があります。

そのため、bundle packageを使ってgemをアプリに含めてしまうと、スラグのサイズがすぐに問題になります。
なので、パッケージ化は行わず、gemfile.lockをgit管理に含めるようにして運用する方が良いでしょう。

このサイズや時間が小さいと感じるかもしれませんが、herokuの考え方に乗っかり "アプリケーションはできるだけ小さく保つ"という方針で運用した方が良いかと思います。

参考: Limits: Slug size

色々なアドオンの利用

herokuでは多くのアドオンを利用できます。
もちろん外部のサービスを別途利用することもできますが、アドオンを利用した方がお手軽です。

一般的に利用されるアドオンは次のようなものです。

  • heroku-postgres
    • postgresqlデータベース。
  • heroku-redis
    • NoSQLデータベース
  • PaperTrail
    • ログ監視サービス: ログの表示とログからアラートで通知させる
  • Librato
    • メトリクス監視サービス: アプリケーション及びミドルウェアを監視してアラートで通知させる
  • New Relic
    • パフォーマンス監視サービス: アプリケーション及びミドルウェアのパフォーマンスを監視してアラートで通知させる
  • Pingdom
    • 外形監視サービス: 外側からアプリケーションを監視して、サービスに関するアラートを通知させる
  • Rollbar
    • エラー監視サービス: 開発者向けにエラー情報の分析と監視を行う
  • Fastly
    • CDN: 即時パージが可能なCDN
  • Cloudinary
    • 画像最適化サービス(CDN): 画像のトリミング、加工、デバイス最適化を行う。CDN経由でDeliverされる
  • SendGrid
    • メール配信サービス
  • PointDNS

監視系のサービスは一通り追加して試してみることをお勧めします。
Pingdomには無償プランがありませんが、PaperTrail, Librato, Rollbar, New Relicには無償プランがあります。

使ってみて良さそうだと思ったアドオンを利用してください。

postgresのプランアップグレードは別DBになる

postgresのプランアップグレードを行う場合、そのプランのpostgresアドオンが、今のpostgresとは別に追加される形になります。

postgres-devide-into-2-db.png

そのため、サービス稼働後にpostgresのプランを上げる場合

  1. メンテナンスモードにする
  2. 上位プランのpostgresを追加する
  3. 今のpostgresから上位プランのpostgresにデータをコピーする
  4. 接続用の環境変数を変更する
  5. メンテナンスモードを解除する

という手順を踏むので、サービスを一時的に停止する必要があります。

サービスの見込みユーザ数とデータ量によって、選択する初期プランはプロジェクトによって違うと思いますが、ある程度の余裕を見越してプランを選択した方が良いかもしれません。

heroku-postgresのバックアップやデータ移行については、こちらの記事(heroku postgresのバックアップで利用するコマンド一覧)も参考にしてください。

postgresおよびmysqlのdb:reset

heroku-postgresを rake db:reset することは権限上できません。
以下のようなエラーになります。

FATAL:  permission denied for database "postgres"
DETAIL:  User does not have CONNECT privilege.
  • postgresをリセットする場合は以下のコマンドを利用します。
heroku pg:reset DATABASE

mysqlは rake db:reset でリセット可能ですが、環境変数を設定する必要があります。
環境変数を DISABLE_DATABASE_ENVIRONMENT_CHECK=1 に設定して、DBの環境チェックを無効にしておきます。
この変数はコマンドに渡しても、サーバの環境変数に設定しても良いです。

# コマンドでresetする場合
DISABLE_DATABASE_ENVIRONMENT_CHECK=1 bundle exec rake db:reset

# 環境変数に設定する場合
heroku config:set DISABLE_DATABASE_ENVIRONMENT_CHECK=1

参考:
Heroku Postgres: pg-reset
Container-Ready Rails 5: Safer Database Actions

Postgresのメンテナンス

heroku Postgres のアドオンは、herokuによってメンテナンスが行われます。
メンテナンスの1~2週間前に、メンテナンス予告の以下のようなメールが送信されてきます。
メンテナンスは、セキュリティパッチやアップグレードのために行われます。

subject: [app-name] add-on (postgresql-your-pg-id) Your Postgres requires maintenance.

We plan on performing this maintenance at 2019-11-07 17:00:00 +0000 
during your set maintenance window of Thursdays 17:00 to 21:00 UTC.

(中略)

You may choose a new or more appropriate maintenance window, as needed. 
For example, you may run: heroku pg:maintenance:window SILVER "Tuesday 14:30"
 to set a maintenance window for Tuesdays at 2:30pm UTC.

メールに記載の通り、指定された日時にメンテナンスが行われます。
メンテナンス時間は最大で4時間ですが、実際には10分程度で終わることも多いです。
この際、PostgresがHA構成の場合は、可能な限りフェイルオーバを行います。

また、必要に応じて、メンテナンスの開始の曜日と時刻を指定しておくことができます。

指定する場合は、herokuコマンドで以下のように行います。
時刻はUTCでの指定になるので、日本時間に換算してください。
JSTで日曜日の午前2:00に開始する場合であれば、UTCでは土曜日の17:00を指定しておきます。

heroku pg:maintenance:window DATABASE "Saturday 17:00" -a sushi

設定した内容は以下のコマンドで参照できます。

heroku pg:maintenance DATABASE -a sushi

必要に応じてマニュアルでの実行も可能です。

heroku maintenance:on -a sushi
Enabling maintenance mode for ⬢ sushi... done

heroku pg:maintenance:run DATABASE -a sushi
Starting maintenance for postgresql-clean-29349... done

heroku maintenance:off -a sushi
Disabling maintenance mode for ⬢ sushi... done

事前にアプリケーションに影響の少ない曜日と時間帯を指定して自動的にアップデートできるか、手動実行した方が良いかは、アプリケーションの性質によるかと思います。

参考: Heroku Postgres Maintenance Windows

ACMとLet's encryptでSSL証明書更新を自動化できる

ACM(Automated Certificate Management)を使って、無料SSLサーバ証明書の Let's encrypt でSSL証明書を自動更新することができます。

以下のコマンドで自動更新を有効にするか、GUIから設定します。

heroku certs:auto:enable

自動化には、いくつかの制限があるので注意してください。

  • SSL Endpointのアドオンはサポートしていません
  • CloudFrontやFastlyなどのCDNとの互換性はありません
  • IPv6レコードはサポートしていません
  • ワイルドカードカスタムドメインはサポートしていません

などです。

ワイルドカード証明書を利用したい場合は、

などのアドオンが代替案として提示されています。

ACMを使ったSSL証明書の自動更新は、上記のとおり、CDNを利用している環境では利用できません。

参考: Automated Certificate Management

パイプラインでReview Appsを活用する

パイプラインには、ReviewAppsという機能が用意されています。

この機能は、herokuのパイプラインとgithubと連携している際に、プルリクエストが作成されると、それに対応した環境をコピー元の環境から作成して、一時的なレビュー環境を自動的に構築してくれます。

コピー元環境からのコピーの設定は、app.jsonに記述しておきます。
このファイルには主に以下の設定を記述できます。

  • 環境変数の定義
  • コピー元から継承する環境変数
  • 利用するアドオン
  • dynoのタイプと数
  • 利用するビルドパック

ReviewAppsを使うことで、developmentやstaging環境に上げる前に、heroku上の環境で動作の確認をすることができます。

github側からもプルリク作成時にReviewAppsの環境にリンクが貼られるので、レビューが非常にやりやすくなります。

参考:
Review Apps
app.json Schema

パイプラインをgit-flowに適用させて運用する

パイプラインを作成する場合、gitのブランチ運用をgit-flowで行うと、自動デプロイやReviewsAppsとうまく適合します。

例えば、ReviewApps,develop,staging,productionのフル構成で、パイプラインを構築して、git-flowを適用すると、以下のようになります。

命名規則は適当につけたものなので、適宜お好きなものを利用してください。

pipeline上の環境 環境の用途 対応ブランチ/デプロイ 命名規則
ReviewApps プルリクの動作確認用 プルリクが作成された時
development 開発・テスト用 Developブランチへのマージ時 sample-app-dev
staging 本番前確認用 Masterブランチへのマージ時 sample-app-stg
production 本番環境 stagingからの昇格(プロモート) sample-app

これを、herokuのパイプラインに適用すると、以下のような感じになります。

pipeline.png

  • ReviewAppsを有効にして、プルリクごとにReviewAppsが起動するようにします
  • developmentをDevelopへのマージをフックし、自動デプロイします。
  • stagingをMasterへのマージをフックし、自動デプロイします。
  • productionへは "Promote to production..."を使って昇格させます。

パイプラインの運用時の注意点

連携するアカウント

pipelineの運用時には、github連携するアカウントに注意します。
会社/チームでの開発では、github連携するアカウントに個人のアカウントは使用しないようにします。

個人のアカウントを設定すると、その方が辞めてしまった場合などに、全ての連携を解除して設定し直す必要があります。
そのため、会社/チームのアカウントでgithub連携を設定しておきましょう。

productionからstagingへのデータコピースクリプトを組む

productionからstaging環境へのデータコピースクリプトを組んでおき、staging環境でレスポンス確認などを行うことをお勧めします。
コピースクリプトは、postgresを利用している場合は簡単に組むことができます。
以下の記事を参考にしてください。
参考: Direct database-to-database copies

stagingに持ってくるデータ内の個人情報のマスクには十分注意してください。
また、メールアドレスなどは、運用上間違ってメールを誤送信する可能性があるため、マスクしない場合でも置き換えが必要です。
その他環境によって考慮が必要な項目は他にもあるかと思います。
staging上のデータには十分配慮してください。

個人情報のマスクとメールアドレスなどの置換まで合わせて、RailsであればRake-Taskにしておくなどの、自動化をお勧めします。

Vue.js, ReactなどのSPAのProductionへのPromoteについて

Railsのパイプラインの運用時には特に問題になりませんが、SPAフレームワークを利用している際のパイプラインの運用では、Production環境へのPromoteボタンでのプロモートは避けた方が無難です。

SPA利用時には、Promoteボタンによるプロモートを行うと、ビルド済みのパッケージが本番にデプロイされるため、環境変数などが変更されません。
環境変数はSPAのビルド時に埋め込まれるため、例えば、stagingに配置されているビルドを本番へPromoteしても、呼び出されるAPIがstagingのままになる、ということが起こり得ます。

この問題(?)については、ReactやVueでも話題になっています。

これだけで1記事書けそうなボリュームではあるのですが、動的に環境変数を読み込むか?という話について、留意したいと感じた部分は以下のようなところです。

  • コンパイル環境変数とランタイム環境変数は分けて考えるべき
    • コンパイル環境変数はコンパイルされたら固定される
    • ランタイム環境変数は動的に読み込まれる
    • ランタイム環境変数はビルドのセット以外のところにenv.jsなどで配置しておき、それを読み込むなどの方式を使う
  • staging から production 環境へのプロモート時は、環境が違うのだからきちんとビルドプロセスを通すべき

なので、基本的には、以下のような方針が良いのではないかと思います。

  • Promoteボタンを使わずにmasterブランチをマニュアルデプロイするなどしてビルドを通す
  • 環境変数は、コンパイル時と動的(ランタイム)時を明確に分ける
  • ランタイム環境変数がどうしても必要な場合には、外部ファイル化などを検討する

Buildpackについて

Buildpack(ビルドパック)はdynoで実行するSlug(スラグ)への変換を行います。
SlugはSlugコンパイラによってコンパイルされます。

ビルドパックは、公式から提供されているものと、サードパーティ製に分かれます。
公式から提供されているものは以下から確認できます。

サードパーティ製のものを含めたビルドパックは以下から検索できます。

公式のものはサポートされていますが、サードパーティ製のものはサポートされていません。
サードパーティ製のビルドパックを使用する場合は、デプロイ数やスター数を参考にしてください。
当然ながら利用は自己責任になります。

bundlerとnpmの両方を利用する場合はビルドパックを追加する

Railsであれば、gem管理には通常bundlerを利用していると思います。
ただ、それ以外にも、npmなども一緒に利用するケースも結構あるかと思います。

npmを使ってnode moduleを利用したい場合は、rubyであれば通常の heroku/ruby 以外に、heroku/nodejs のビルドパックも必要になります。

ビルドパックの適用順序によってはきちんと動作しないこともあります。
Railsでnpmも同時に使用する場合は、ビルドパックを以下の順序にしておきます。

buildpack.png

heroku上のアプリケーションのセキュリティ

herokuの基本的なセキュリティについては以下のドキュメントを参考にしてください。

アプリケーションレベルのDDosなどの攻撃には、いくつかの方法での対応することができます。

例えば、Railsであれば、rack-attack
NodeJSであれば、express-rate-limit
などを入れても良いでしょう。

DDosやSynFloodについては、CDNを使っている場合はCDN側で対応します。

その他のセキュリティについては、現代的なフレームワークを使っているのであれば、フレームワークに則ってコードを記述していれば、脆弱性を作ってしまうことはあまりないかと思います。
しかし、不慣れなデベロッパが埋め込む脆弱性や、フレームワーク自体の脆弱性などに別途対応するためには、WAFなどの仕組みを通す必要があります。

通常ならば、利用しているCDN側にDDosプロテクションやWAFの機能があるので、CDN側の機能を利用します。

脆弱性継続検知の目的では、herokuでは以下のアドオンが用意されているようです。

利用コストが高いので、Vaddyなどの外部サービスを利用しても良いと思います。
Vaddyの場合はCircle CIとの連携が可能なので、CircleCIを使っている場合は、デプロイ時に検査を走らせることもできます。

heroku / rails / postgresのタイムゾーンの設定

バックエンドにDBを利用する場合の、herokuのWebアプリケーションで、タイムゾーンの設定を行う箇所は、少なくとも3箇所あります。

  1. heroku osのタイムゾーン設定
  2. railsのタイムゾーン設定
  3. postgresのタイムゾーン設定

です。

アプリケーションを提供するロケーションが日本だけの場合でも、タイムゾーンの設定方針には大まかに二つあります。

  • 全てをJSTで統一する
  • DB側のみUTCにする

ここでは全てをJSTに統一するケースの説明をします。
全てをJSTに統一した際のメリットは、データを直に見た際に、日時を日本時間として計算せずに直感的に扱える点が大きいかと思います。

以下のコマンド実行には、heroku-cliが必要です。

  • 1. heroku osのタイムゾーンは、"TZ"の環境変数で設定します
heroku config:add TZ=Asia/Tokyo --app your_application_name

参考: Change the time zone on a Heroku app

  • 2. railsのタイムゾーン設定は、application.rbで行います
application.rb
config.time_zone = 'Tokyo'
config.active_record.default_timezone = :local

参考: Railsタイムゾーンまとめ

  • 3. postgresのタイムゾーンの設定は、alter database構文で行います
# アプリケーションのpostgresに接続する
heroku pg:psql --app your_application_name
-- alter databaseでtimezoneを変更する
show timezone;
alter database your_database_name set timezone = 'Asia/Tokyo';
select current_timestamp;
\q

参考: How to change Timezone for Postgres?

heroku scheduler か workerか

schedulerを利用するケース

herokuには定期的なバッチ実行のために、heroku schedulerが用意されています。
add-onに追加すれば無償で利用可能ですが、注意点があります。

  • 実行時間に多少のずれがある
  • バッチは週、曜日、月などの指定はできず、スパンは、10分ごと, 1時間ごと, 1日ごとのみ選択可能
  • リアルタイムなリクエストには利用できない

サーバでcronのような定期ジョブを回すという使い方では、スケジューラでも十分に役割を果たすことができます。
例えば、週1や月1で実行したいジョブを作る場合には、1日ごとに起動するジョブを使って、特定の日付でのみ実行されるように組むことで対応が可能です。

cronと同じような仕組みでジョブを流したい場合には、有償ですが、以下のアドオンの追加が推奨されています。

workerを利用するケース

herokuでは、リクエストタイムアウトが30秒に設定されています。

そのため、ユーザ要求にしたがって行う処理が30秒を超える場合、そのままでは処理できません。
その代わりにworkerを使って処理を行います。

ユーザからのリクエストを受けたアプリケーションは、workerに処理を移譲して、バックグラウンドで処理を実行します。
RailsであればSidekiqなどを使うことでworkerに処理を投げることができます。
workerはバックグランドで実行されるので、リクエストタイムアウトが適用されません。

SchedulerとWorkerは以下のように使い分けると良いかと思います。

  • 定期で実行されるジョブ(通常cronなどで実行するもの) => Scheduler
  • ユーザのリクエストに応じて実行される時間のかかる処理 => Worker

herokuでのcookie情報のサブドメイン間共有について

何らかの要件で、cookieの一部の情報を、アプリケーション間で共有したいという需要はあると思います。
一般的にはこれらに対応するため、クッキーのサブドメイン間での共有の方法が用意されています。

ただし、herokuドメインでは、このクッキーのサブドメイン間共有は利用できません。

herokuapp.com は、Mozilla FoundationのPublic Suffix Listに登録されており、FirefoxやChromeなど最近のブラウザでは *.herokuapp.com を使ったクッキーの設定は拒否されてしまいます。

サブドメイン間のクッキー共有を利用する場合は、独自ドメインを使う必要があります。
企業で利用する場合は、独自ドメインを割り当てて利用すると思いますので、そちらを利用するようにする必要があります。

参考: Cookies and the Public Suffix List

herokuの設計思想

herokuを利用していると、どうしてそういう仕様になっているのか知りたくなることがあります。
herokuの設計思想について疑問がある時は、herokuの共同創始者であるAdam Wigginsが書いている

を読むと、それが分かるかもしれません。
こちらは、herokuについて書かれている訳ではありません。アプリケーションを問わず守っておくと、利用しやすさやポータビリティ、メンテナビリティなどを維持しておける要素を紹介している記事です。
herokuも当然この考え方の上になりたっているので、heorku上にアプリをデプロイすると、必然的にこれらの要素を守っている状態になります。

参考: https://blog.heroku.com/twelve-factor-apps

最後に

heroku環境を初めて利用する際には、当然ながら覚えることが多少あります。
1からサーバを立てるのに慣れている人だと、herokuを使う方が面倒だと感じることもあるかもしれません。

ですが、heroku環境に慣れてくると、1からサーバを立てるよりも遥かに短いステップで環境を構築することができます。
豊富なアドオンを使うことで、大幅に構築コストを抑えることができます。

多くのアドオンが無償版を用意していることもあり、まずherokuに触ってみるというのが非常にやりやすくなっています。

まずは試してみて、herokuで本番環境まで運用できるという感触を掴んでみるのが良いと思います。

長々と読んでいただき、ありがとうございました。

undrthemt
Rails Engineer. Amami lover. 少しでもフルスタックに近づきたい。 服好きがとまらない。 公用語英語の多国籍devチームで奮闘中。 #rails #phper #rest_api #graphql #vuejs #reactjs #heroku #aws #amami
http://undrthemt.info/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away