├── .gitignore
├── .prettierrc
├── README.md
├── docker-python-flask-redis
├── .gitignore
├── Dockerfile
├── README.md
├── app.py
├── docker-compose.yml
├── requirements.txt
└── templates
│ └── index.html
├── recipe-1
├── README.md
├── docker-compose.yml
├── docker
│ └── app
│ │ └── Dockerfile
└── images
│ ├── docker-01.png
│ ├── docker-02.png
│ ├── docker-compose-01.png
│ ├── docker-compose-02.png
│ ├── docker-compose-03.png
│ ├── docker-compose-04.png
│ ├── docker-compose-05.png
│ └── docker-compose-06.png
├── recipe-2-0
├── README.md
├── docker-compose.yml
├── docker
│ ├── express-app
│ │ └── Dockerfile
│ └── react-app
│ │ └── Dockerfile
├── images
│ ├── docker-01.png
│ ├── docker-02.png
│ ├── docker-03.png
│ ├── docker-04.png
│ ├── docker-05.png
│ ├── docker-06.png
│ ├── docker-07.png
│ ├── docker-08.png
│ ├── docker-09.png
│ ├── docker-10.png
│ ├── docker-compose-01.png
│ ├── docker-compose-02.png
│ ├── docker-compose-03.png
│ ├── docker-compose-04.png
│ ├── docker-compose-05.png
│ └── docker-compose-06.png
└── sample
│ ├── express-app
│ ├── example.js
│ ├── index.js
│ └── views
│ │ └── index.pug
│ └── react-app
│ ├── Index.js
│ └── components
│ ├── App.js
│ └── Example.js
├── recipe-2-1
├── README.md
├── docker-compose.yml
├── docker
│ ├── express-app
│ │ └── Dockerfile
│ ├── mysql
│ │ ├── Dockerfile
│ │ └── conf.d
│ │ │ └── my.cnf
│ └── react-app
│ │ └── Dockerfile
└── sample
│ ├── express-app
│ ├── config
│ │ ├── db.js
│ │ └── index.js
│ ├── index.js
│ ├── router
│ │ └── api
│ │ │ └── users.js
│ └── views
│ │ └── index.pug
│ └── react-app
│ ├── Index.js
│ └── components
│ └── App.js
├── recipe-2-2
├── README.md
├── docker-compose.yml
├── docker
│ ├── express-app
│ │ └── Dockerfile
│ ├── mysql
│ │ ├── Dockerfile
│ │ └── conf.d
│ │ │ └── my.cnf
│ └── react-app
│ │ └── Dockerfile
└── sample
│ ├── express-app
│ ├── api
│ │ ├── csrfToken.js
│ │ ├── login.js
│ │ ├── logout.js
│ │ ├── tasks.js
│ │ └── users.js
│ ├── config
│ │ ├── db.js
│ │ ├── index.js
│ │ └── jwt.config.js
│ ├── index.js
│ ├── middlewares
│ │ ├── validator.js
│ │ └── verifyToken.js
│ ├── package.json
│ └── views
│ │ └── index.pug
│ └── react-app
│ └── src
│ ├── Index.js
│ ├── app
│ └── store.js
│ ├── components
│ ├── App.js
│ ├── Footer.js
│ ├── Header.js
│ ├── Layout.js
│ ├── Login.js
│ ├── Main.js
│ ├── Modal.js
│ ├── SignUp.js
│ ├── Task.js
│ ├── TaskDeleteModal.js
│ ├── TaskInsertModal.js
│ └── TaskUpdateModal.js
│ ├── config
│ └── index.js
│ ├── features
│ ├── auth
│ │ └── authSlice.js
│ └── tasks
│ │ └── tasksSlice.js
│ └── index.css
├── recipe-2.5
├── README.md
├── docker-compose.yml
├── docker
│ ├── express-app
│ │ └── Dockerfile
│ ├── mysql
│ │ ├── Dockerfile
│ │ └── conf.d
│ │ │ └── my.cnf
│ └── react-app
│ │ └── Dockerfile
└── sample
│ ├── express-app
│ ├── api
│ │ ├── login.js
│ │ └── users.js
│ ├── config
│ │ └── jwt.config.js
│ ├── index.js
│ └── middlewares
│ │ └── verifyToken.js
│ └── react-app
│ ├── Index.js
│ └── components
│ └── App.js
├── recipe-docker-nginx-express
├── README.md
├── docker-compose.yml
└── docker
│ ├── api
│ ├── Dockerfile
│ ├── index.js
│ └── package.json
│ └── nginx
│ ├── Dockerfile
│ └── conf.d
│ └── default.conf
├── recipe-express-socketio-redis
├── README.md
├── demo.gif
├── docker-compose.yml
└── docker
│ └── express-app
│ ├── Dockerfile
│ ├── index.html
│ └── main.js
├── recipe-https-nginx-typescript-react-express-mysql
├── .gitignore
├── README.md
├── docker-compose.yml
├── docker
│ ├── express-app
│ │ └── Dockerfile
│ ├── mysql
│ │ ├── Dockerfile
│ │ └── conf.d
│ │ │ └── my.cnf
│ ├── nginx
│ │ ├── Dockerfile
│ │ └── conf.d
│ │ │ └── default.conf
│ └── react-app
│ │ └── Dockerfile
└── sample
│ ├── express-app
│ └── index.ts
│ └── react-app
│ └── App.tsx
├── recipe-mysql-dockerfile
├── README.md
└── docker
│ └── mysql
│ ├── Dockerfile
│ └── conf.d
│ └── my.cnf
├── recipe-mysql-no-dockerfile
└── README.md
├── recipe-nginx-react-express-mysql
├── README.md
├── docker-compose.yml
└── docker
│ ├── express-app
│ └── Dockerfile
│ ├── mysql
│ ├── Dockerfile
│ └── conf.d
│ │ └── my.cnf
│ ├── nginx
│ ├── Dockerfile
│ └── conf.d
│ │ └── default.conf
│ └── react-app
│ └── Dockerfile
├── recipe-postgresql-dockerfile
├── README.md
└── docker
│ └── postgresql
│ └── Dockerfile
├── recipe-python-flask-blueprint
├── .gitignore
├── README.md
├── docker
│ └── flask
│ │ ├── Dockerfile
│ │ └── requirements.txt
└── sample
│ └── app
│ ├── __init__.py
│ ├── api
│ ├── __init__.py
│ └── users.py
│ └── templates
│ └── base.html
├── recipe-rails-postgresql
├── README.md
├── docker-compose.yml
└── docker
│ ├── postgresql
│ └── Dockerfile
│ └── rails
│ ├── Dockerfile
│ ├── Gemfile
│ ├── Gemfile.lock
│ └── database.yml
├── recipe-volume-share
├── README.md
├── docker-compose.yml
└── docker
│ ├── express-app
│ ├── Dockerfile
│ ├── index.js
│ └── package.json
│ └── nginx
│ ├── Dockerfile
│ └── conf.d
│ └── default.conf
├── recipe-vscode-git-github-https
├── README.md
└── images
│ ├── git-00.png
│ ├── git-01.png
│ ├── git-02.png
│ ├── git-03.png
│ └── git-04.png
└── tips
├── README.md
├── mysql-backup-restore
└── README.md
└── network-node-mysql
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | app/*
2 | # Created by https://www.toptal.com/developers/gitignore/api/macos,windows,node
3 | # Edit at https://www.toptal.com/developers/gitignore?templates=macos,windows,node
4 |
5 | ### macOS ###
6 | # General
7 | .DS_Store
8 | .AppleDouble
9 | .LSOverride
10 |
11 | # Icon must end with two \r
12 | Icon
13 |
14 |
15 | # Thumbnails
16 | ._*
17 |
18 | # Files that might appear in the root of a volume
19 | .DocumentRevisions-V100
20 | .fseventsd
21 | .Spotlight-V100
22 | .TemporaryItems
23 | .Trashes
24 | .VolumeIcon.icns
25 | .com.apple.timemachine.donotpresent
26 |
27 | # Directories potentially created on remote AFP share
28 | .AppleDB
29 | .AppleDesktop
30 | Network Trash Folder
31 | Temporary Items
32 | .apdisk
33 |
34 | ### Node ###
35 | # Logs
36 | logs
37 | *.log
38 | npm-debug.log*
39 | yarn-debug.log*
40 | yarn-error.log*
41 | lerna-debug.log*
42 |
43 | # Diagnostic reports (https://nodejs.org/api/report.html)
44 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
45 |
46 | # Runtime data
47 | pids
48 | *.pid
49 | *.seed
50 | *.pid.lock
51 |
52 | # Directory for instrumented libs generated by jscoverage/JSCover
53 | lib-cov
54 |
55 | # Coverage directory used by tools like istanbul
56 | coverage
57 | *.lcov
58 |
59 | # nyc test coverage
60 | .nyc_output
61 |
62 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
63 | .grunt
64 |
65 | # Bower dependency directory (https://bower.io/)
66 | bower_components
67 |
68 | # node-waf configuration
69 | .lock-wscript
70 |
71 | # Compiled binary addons (https://nodejs.org/api/addons.html)
72 | build/Release
73 |
74 | # Dependency directories
75 | node_modules/
76 | jspm_packages/
77 |
78 | # TypeScript v1 declaration files
79 | typings/
80 |
81 | # TypeScript cache
82 | *.tsbuildinfo
83 |
84 | # Optional npm cache directory
85 | .npm
86 |
87 | # Optional eslint cache
88 | .eslintcache
89 |
90 | # Optional stylelint cache
91 | .stylelintcache
92 |
93 | # Microbundle cache
94 | .rpt2_cache/
95 | .rts2_cache_cjs/
96 | .rts2_cache_es/
97 | .rts2_cache_umd/
98 |
99 | # Optional REPL history
100 | .node_repl_history
101 |
102 | # Output of 'npm pack'
103 | *.tgz
104 |
105 | # Yarn Integrity file
106 | .yarn-integrity
107 |
108 | # dotenv environment variables file
109 | .env
110 | .env.test
111 | .env*.local
112 |
113 | # parcel-bundler cache (https://parceljs.org/)
114 | .cache
115 | .parcel-cache
116 |
117 | # Next.js build output
118 | .next
119 |
120 | # Nuxt.js build / generate output
121 | .nuxt
122 | dist
123 |
124 | # Storybook build outputs
125 | .out
126 | .storybook-out
127 | storybook-static
128 |
129 | # rollup.js default build output
130 | dist/
131 |
132 | # Gatsby files
133 | .cache/
134 | # Comment in the public line in if your project uses Gatsby and not Next.js
135 | # https://nextjs.org/blog/next-9-1#public-directory-support
136 | # public
137 |
138 | # vuepress build output
139 | .vuepress/dist
140 |
141 | # Serverless directories
142 | .serverless/
143 |
144 | # FuseBox cache
145 | .fusebox/
146 |
147 | # DynamoDB Local files
148 | .dynamodb/
149 |
150 | # TernJS port file
151 | .tern-port
152 |
153 | # Stores VSCode versions used for testing VSCode extensions
154 | .vscode-test
155 |
156 | # Temporary folders
157 | tmp/
158 | temp/
159 |
160 | ### Windows ###
161 | # Windows thumbnail cache files
162 | Thumbs.db
163 | Thumbs.db:encryptable
164 | ehthumbs.db
165 | ehthumbs_vista.db
166 |
167 | # Dump file
168 | *.stackdump
169 |
170 | # Folder config file
171 | [Dd]esktop.ini
172 |
173 | # Recycle Bin used on file shares
174 | $RECYCLE.BIN/
175 |
176 | # Windows Installer files
177 | *.cab
178 | *.msi
179 | *.msix
180 | *.msm
181 | *.msp
182 |
183 | # Windows shortcuts
184 | *.lnk
185 |
186 | # End of https://www.toptal.com/developers/gitignore/api/macos,windows,node
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "semi": false
4 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Docker-DockerCompose-Training
2 |
3 | ## 公式ドキュメント
4 |
5 | [docker docs](https://docs.docker.com/)
6 |
7 | ## 動作環境
8 |
9 | [Docker](https://www.docker.com/)、[Docker Compose](https://docs.docker.com/compose/)が動作すること([Docker Desktop](https://www.docker.com/get-started)で可能)
10 |
11 | ## recipe
12 |
13 | - [tips](./tips/)
14 | 1. [mysql-backup-restore](./tips/mysql-backup-restore/)
15 | 1. MySQL コンテナのバックアップリストア
16 | 2. [network-node-mysql](./tips/network-node-mysql/)
17 | 1. docker の network 機能を用いてコンテナ間の通信の設定サンプル。 具体的には node(`node-app`)コンテナと mysql(`mysqld`)コンテナをコンテナ名で通信できるよう設定する
18 | - [recipe-1](./recipe-1/README.md)
19 | 1. Docker で環境構築
20 | 1. docker exec で接続し開発
21 | 1. VSCode から接続し開発
22 | 1. Docker の基本的な操作
23 | 2. Docker Compose で環境構築
24 | 1. Docker をマウントし開発
25 | 1. VSCode から接続し開発
26 | 1. Docker Compose の基本的な操作
27 | - [recipe-2-0](./recipe-2-0/README.md)
28 | 1. 全体を通して Docker、Docker Compose の基本的な使い方の理解
29 | 2. 全体を通して VSCode から Docker コンテナに接続しアプリケーションコードを作成
30 | 3. docker を用いてフロントサーバ、API サーバをたて連携(Part 1)
31 | 4. 3 の環境構築を Docker Compose で行う(Part 2)
32 | 5. (Training) 4 で改めて環境構築した環境で 3 のアプリを実装(Part 3)し他の機能も追加する
33 | - [recipe-2-1](./recipe-2-1/README.md)
34 | 1. [recipe-2-0](./recipe-2-0/README.md) に MySQL を追加、express-app を router で書き直し
35 | - [recipe-2-2](./recipe-2-2/README.md)
36 | 1. [recipe-2-0](./recipe-2-0/README.md),[recipe-2-1](./recipe-2-1/README.md) に jwt,cookie,csrf を追加
37 | - [recipe-mysql-dockerfile](./recipe-mysql-dockerfile/README.md)
38 | 1. Dockerfile を用いた MySQL の環境構築
39 | - [recipe-mysql-no-dockerfile](./recipe-mysql-no-dockerfile/README.md)
40 | 1. Dockerfile を用いずコマンドのみでの MySQL の環境構築
41 | - [recipe-python-flask-blueprint](./recipe-python-flask-blueprint/README.md)
42 | 1. Docker で Flask の開発環境(Blueprint を用いたサンプルコード`sample`配下まで配置)の構築例
43 | - [recipe-rails-postgresql](./recipe-rails-postgresql)
44 | 1. rails + PostgreSQL の環境
45 | - [recipe-postgresql-dockerfile](./recipe-postgresql-dockerfile)
46 | 1. PostgreSQL 単体の環境
47 | - [recipe--nginx-react-express-mysql]
48 | 1. Nginx + React + Express + MySQL の環境レシピ(React は build image を Nginx に volume の共有で認識させる)
49 | - [recipe-https-nginx-typescript-react-express-mysql](./recipe-https-nginx-typescript-react-express-mysql)
50 | 1. HTTPS + Nginx + TypeScript + React + Express + MySQL の環境レシピ(HTTPS は Nginx に証明書を設定する、React は build image を Nginx に volume の共有で認識させる)
51 | - [recipe-docker-nginx-express](./recipe-docker-nginx-express)
52 | 1. docker-compose での Web(Nginx) + API(express) の環境構築
53 |
--------------------------------------------------------------------------------
/docker-python-flask-redis/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by https://www.toptal.com/developers/gitignore/api/python,macos
2 | # Edit at https://www.toptal.com/developers/gitignore?templates=python,macos
3 |
4 | ### macOS ###
5 | # General
6 | .DS_Store
7 | .AppleDouble
8 | .LSOverride
9 |
10 | # Icon must end with two \r
11 | Icon
12 |
13 |
14 | # Thumbnails
15 | ._*
16 |
17 | # Files that might appear in the root of a volume
18 | .DocumentRevisions-V100
19 | .fseventsd
20 | .Spotlight-V100
21 | .TemporaryItems
22 | .Trashes
23 | .VolumeIcon.icns
24 | .com.apple.timemachine.donotpresent
25 |
26 | # Directories potentially created on remote AFP share
27 | .AppleDB
28 | .AppleDesktop
29 | Network Trash Folder
30 | Temporary Items
31 | .apdisk
32 |
33 | ### Python ###
34 | # Byte-compiled / optimized / DLL files
35 | __pycache__/
36 | *.py[cod]
37 | *$py.class
38 |
39 | # C extensions
40 | *.so
41 |
42 | # Distribution / packaging
43 | .Python
44 | build/
45 | develop-eggs/
46 | dist/
47 | downloads/
48 | eggs/
49 | .eggs/
50 | lib/
51 | lib64/
52 | parts/
53 | sdist/
54 | var/
55 | wheels/
56 | share/python-wheels/
57 | *.egg-info/
58 | .installed.cfg
59 | *.egg
60 | MANIFEST
61 |
62 | # PyInstaller
63 | # Usually these files are written by a python script from a template
64 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
65 | *.manifest
66 | *.spec
67 |
68 | # Installer logs
69 | pip-log.txt
70 | pip-delete-this-directory.txt
71 |
72 | # Unit test / coverage reports
73 | htmlcov/
74 | .tox/
75 | .nox/
76 | .coverage
77 | .coverage.*
78 | .cache
79 | nosetests.xml
80 | coverage.xml
81 | *.cover
82 | *.py,cover
83 | .hypothesis/
84 | .pytest_cache/
85 | cover/
86 |
87 | # Translations
88 | *.mo
89 | *.pot
90 |
91 | # Django stuff:
92 | *.log
93 | local_settings.py
94 | db.sqlite3
95 | db.sqlite3-journal
96 |
97 | # Flask stuff:
98 | instance/
99 | .webassets-cache
100 |
101 | # Scrapy stuff:
102 | .scrapy
103 |
104 | # Sphinx documentation
105 | docs/_build/
106 |
107 | # PyBuilder
108 | .pybuilder/
109 | target/
110 |
111 | # Jupyter Notebook
112 | .ipynb_checkpoints
113 |
114 | # IPython
115 | profile_default/
116 | ipython_config.py
117 |
118 | # pyenv
119 | # For a library or package, you might want to ignore these files since the code is
120 | # intended to run in multiple environments; otherwise, check them in:
121 | # .python-version
122 |
123 | # pipenv
124 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
125 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
126 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
127 | # install all needed dependencies.
128 | #Pipfile.lock
129 |
130 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
131 | __pypackages__/
132 |
133 | # Celery stuff
134 | celerybeat-schedule
135 | celerybeat.pid
136 |
137 | # SageMath parsed files
138 | *.sage.py
139 |
140 | # Environments
141 | .env
142 | .venv
143 | env/
144 | venv/
145 | ENV/
146 | env.bak/
147 | venv.bak/
148 |
149 | # Spyder project settings
150 | .spyderproject
151 | .spyproject
152 |
153 | # Rope project settings
154 | .ropeproject
155 |
156 | # mkdocs documentation
157 | /site
158 |
159 | # mypy
160 | .mypy_cache/
161 | .dmypy.json
162 | dmypy.json
163 |
164 | # Pyre type checker
165 | .pyre/
166 |
167 | # pytype static type analyzer
168 | .pytype/
169 |
170 | # Cython debug symbols
171 | cython_debug/
172 |
173 | # End of https://www.toptal.com/developers/gitignore/api/python,macos
--------------------------------------------------------------------------------
/docker-python-flask-redis/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM python:3.4-alpine
2 | USER root
3 |
4 | ADD . /code
5 | WORKDIR /code
6 |
7 | RUN pip install -r requirements.txt
8 |
--------------------------------------------------------------------------------
/docker-python-flask-redis/README.md:
--------------------------------------------------------------------------------
1 | # python-redis
2 |
3 | python、redis が動作する環境
4 |
5 | python は docker 起動時にはアプリは起動せず docker 起動後に適時起動する(flask の想定。そのため Port5000 番を解放)
6 |
7 | ## run
8 |
9 | ```
10 | docker-compose up -d
11 | ```
12 |
13 | ## stop & start
14 |
15 | ```
16 | docker-compose stop
17 | ```
18 |
19 | ```
20 | docker-compose start
21 | ```
22 |
23 | ## container 内 flask 起動
24 |
25 | アプリをセーブしたら Flask に反映させるため debug,development で実行
26 |
27 | VSCode で接続し Terminal で以下を実行(フォアグランドで実行し続ける)
28 |
29 | ```
30 | FLASK_APP=app.py FLASK_ENV=development flask run
31 | ```
32 |
33 | bash 接続し上と同じく実行
34 |
35 | ```
36 | docker container --it xxx bash
37 | FLASK_APP=app.py FLASK_ENV=development flask run
38 | ```
39 |
--------------------------------------------------------------------------------
/docker-python-flask-redis/app.py:
--------------------------------------------------------------------------------
1 | from flask import Flask, request,render_template
2 | from redis import Redis
3 |
4 | app = Flask(__name__)
5 | redis = Redis(host='redis', port=6379)
6 |
7 | @app.route('/',methods=['GET', 'POST'])
8 | def form():
9 | count = redis.incr('hits')
10 | print(count)
11 | count = redis.decr('hits')
12 | return render_template('index.html')
13 |
14 | if __name__ == "__main__":
15 | app.run(host="0.0.0.0", debug=True)
16 |
--------------------------------------------------------------------------------
/docker-python-flask-redis/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "3"
2 | services:
3 | web:
4 | build: .
5 | ports:
6 | - "5000:5000"
7 | tty: true
8 | volumes:
9 | - .:/code
10 | redis:
11 | image: "redis:alpine"
12 |
--------------------------------------------------------------------------------
/docker-python-flask-redis/requirements.txt:
--------------------------------------------------------------------------------
1 | flask
2 | redis
--------------------------------------------------------------------------------
/docker-python-flask-redis/templates/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Document
9 |
10 |
11 |
12 | hello
13 | hoge
14 |
15 |
16 |
--------------------------------------------------------------------------------
/recipe-1/README.md:
--------------------------------------------------------------------------------
1 | # recipe-1 Docker-DockerCompose-Training
2 |
3 | ## 動作環境
4 |
5 | [Docker](https://www.docker.com/)、[Docker Compose](https://docs.docker.com/compose/)が動作すること([Docker Desktop](https://www.docker.com/get-started)で可能)
6 |
7 | Node.js,npm が動作すること
8 |
9 | ```
10 | $ node -v
11 | v14.15.4
12 | $ npm -v
13 | 7.6.0
14 | $ npx -v
15 | 7.6.0
16 | $ yarn -v
17 | 1.22.10
18 | ```
19 |
20 | ## 公式ドキュメント
21 |
22 | [docker docs](https://docs.docker.com/)
23 |
24 | ## すること
25 |
26 | 1. Docker で環境構築
27 | 1. docker exec で接続し開発
28 | 1. VSCode から接続し開発
29 | 1. Docker の基本的な操作
30 | 1. Docker Compose で環境構築
31 | 1. Docker をマウントし開発
32 | 1. VSCode から接続し開発
33 | 1. Docker Compose の基本的な操作
34 |
35 | ## 1. Docker で環境構築
36 |
37 | ### 今回の作業ディレクトリの作成
38 |
39 | ```
40 | $ mkdir Docker-DockerCompose-Training
41 | $ cd Docker-DockerCompose-Training
42 | ```
43 |
44 | ### サンプルコード用ディレクトリの作成
45 |
46 | React のサンプルコードを Docker コンテナにマウントする際に利用する`app`ディレクトリを作成する
47 |
48 | ```
49 | $ mkdir app
50 | ```
51 |
52 | ### gitignore の作成
53 |
54 | [gitignore.io](https://www.toptal.com/developers/gitignore)で`vscode`を指定して作成する(ローカルで作成しても問題ない)
55 |
56 | `.gitignore`に以下を追記し管理対象から除外
57 |
58 | ```
59 | + app/*
60 | ```
61 |
62 | ### Dockerfile の作成
63 |
64 | `docker/app`ディレクトリを作成し配下に`Node.js`環境用の[`Dockerfile`](./docker/app/Dockerfile)を作成する
65 |
66 | ```
67 | $ mkdir -p docker/app
68 | ```
69 |
70 | ```
71 | $ cat Dockerfile
72 | FROM node:latest
73 |
74 | ENV APP_PATH=/app
75 | RUN mkdir $APP_PATH
76 | WORKDIR $APP_PATH
77 | ```
78 |
79 | ### Docker イメージの確認
80 |
81 | ```
82 | $ docker images
83 | ```
84 |
85 | ### docker build
86 |
87 | `Docker-DockerCompose-Training`から実行
88 |
89 | ```
90 | $ docker build --file=./docker/app/Dockerfile -t sample:1 .
91 | [+] Building 39.6s (7/7) FINISHED
92 | => [internal] load build definition from Dockerfile 0.0s
93 | => => transferring dockerfile: 115B 0.0s
94 | => [internal] load .dockerignore 0.0s
95 | => => transferring context: 2B 0.0s
96 | => [internal] load metadata for docker.io/library/node:latest 4.1s
97 | => [1/3] FROM docker.io/library/node:latest@sha256:def7bb01cc33bc226e2fb94e6b820e5db9a3793e342c21d70e18ed6e3e3cc68a 34.4s
98 | => => resolve docker.io/library/node:latest@sha256:def7bb01cc33bc226e2fb94e6b820e5db9a3793e342c21d70e18ed6e3e3cc68a 0.0s
99 | => => sha256:def7bb01cc33bc226e2fb94e6b820e5db9a3793e342c21d70e18ed6e3e3cc68a 776B / 776B 0.0s
100 | => => sha256:ed78ee5153382701a4ca5d363a56b526fca073841186b70b9ed1c280dee92363 2.21kB / 2.21kB 0.0s
101 | => => sha256:56bc674036dc4e2bd2cd490939d04842c10a0c195c9184363573860c359086fc 7.83kB / 7.83kB 0.0s
102 | => => sha256:16cf3fa6cb1190b4dfd82a5319faa13e2eb6e69b7b4828d4d98ba1c0b216e446 45.38MB / 45.38MB 10.9s
103 | => => sha256:3ddd031622b35dad4be68eec6ac0787b0394f37b3dbb600a04e8b2363297b8d7 11.27MB / 11.27MB 2.4s
104 | => => sha256:69c3fcab77df556f3a56ec3d2a5b5cc304f4c4d4341b6f8423dd86ebe5ddaebb 4.34MB / 4.34MB 2.6s
105 | => => sha256:a403cc031caeb1ddbae71b9f7e47d7854415c8aa6f0b84b8e7be4b3db513867e 49.79MB / 49.79MB 6.3s
106 | => => sha256:b900c5ffbaf4b4884b18eb27cec8a890510d745d6a65ba90efe10c9cdeaaade8 214.34MB / 214.34MB 22.3s
107 | => => sha256:f877dc3acfca02604e73fb01208848941a1718b55297038decc5464f36edd649 4.19kB / 4.19kB 6.5s
108 | => => sha256:6779ae40941d54401cbb2a16e55b2a4e2dcaf635aaf5c0edc1140867660fbd78 34.05MB / 34.05MB 9.7s
109 | => => sha256:cc22e2b1bc09a54e7d1fb7dc84f037cedba749071cc5fa0f9056232054801884 2.38MB / 2.38MB 10.2s
110 | => => sha256:70e96dddd4f16fb4e8cbe8c671d40acdb485440e5346e86eca021b82d7c739e2 294B / 294B 10.4s
111 | => => extracting sha256:16cf3fa6cb1190b4dfd82a5319faa13e2eb6e69b7b4828d4d98ba1c0b216e446 2.5s
112 | => => extracting sha256:3ddd031622b35dad4be68eec6ac0787b0394f37b3dbb600a04e8b2363297b8d7 0.8s
113 | => => extracting sha256:69c3fcab77df556f3a56ec3d2a5b5cc304f4c4d4341b6f8423dd86ebe5ddaebb 0.2s
114 | => => extracting sha256:a403cc031caeb1ddbae71b9f7e47d7854415c8aa6f0b84b8e7be4b3db513867e 2.7s
115 | => => extracting sha256:b900c5ffbaf4b4884b18eb27cec8a890510d745d6a65ba90efe10c9cdeaaade8 9.3s
116 | => => extracting sha256:f877dc3acfca02604e73fb01208848941a1718b55297038decc5464f36edd649 0.1s
117 | => => extracting sha256:6779ae40941d54401cbb2a16e55b2a4e2dcaf635aaf5c0edc1140867660fbd78 1.6s
118 | => => extracting sha256:cc22e2b1bc09a54e7d1fb7dc84f037cedba749071cc5fa0f9056232054801884 0.2s
119 | => => extracting sha256:70e96dddd4f16fb4e8cbe8c671d40acdb485440e5346e86eca021b82d7c739e2 0.0s
120 | => [2/3] RUN mkdir /app 0.9s
121 | => [3/3] WORKDIR /app 0.0s
122 | => exporting to image 0.0s
123 | => => exporting layers 0.0s
124 | => => writing image sha256:22d744614838a22ae305967921f49f179fac325b2f4987a3f54511180075f7bf 0.0s
125 | => => naming to docker.io/library/sample:1
126 | ```
127 |
128 | ### Docker イメージの確認
129 |
130 | build で作成したイメージ`sample`が存在すること
131 |
132 | ```
133 | $ docker images
134 | REPOSITORY TAG IMAGE ID CREATED SIZE
135 | sample 1 43a2ae7ab3ee About a minute ago 936MB
136 | ```
137 |
138 | ### Docker コンテナの確認
139 |
140 | イメージをコンテナと起動していないため何も出力されないこと
141 |
142 | ```
143 | $ docker ps -a
144 | ```
145 |
146 | ### 新しい Docker コンテナの起動
147 |
148 | **`pwd`がローカルで実行できない場合、フルパスを出力し書き換えて実行する**
149 |
150 | ```
151 | $ docker run -v `pwd`/app:/app -it -d --name sample-1 -p 3000:3000 sample:1
152 | ```
153 |
154 | ### Docker コンテナの確認
155 |
156 | **`CONTAINER ID`を今後は指定するので控えること**
157 |
158 | `NAMES`に`sample-1`が存在し`STATUS`が`UP`であること
159 |
160 | ```
161 | $ docker ps -a
162 | CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
163 | 54306bc7ef99 sample:1 "docker-entrypoint.s…" 29 seconds ago Up 28 seconds 0.0.0.0:3000->3000/tcp sample-1
164 | ```
165 |
166 | ### 1.1 docker exec で接続し開発
167 |
168 | `docker ps -a`で確認した`CONTAINER ID`を`-it`以降に指定する
169 |
170 | ```
171 | $ docker exec -it 54306bc7ef99 bash
172 | ```
173 |
174 | #### ファイルの作成と確認
175 |
176 | ```
177 | # touch hoge
178 | # ls -la
179 | total 8
180 | drwxr-xr-x 2 root root 4096 Feb 8 06:20 .
181 | drwxr-xr-x 1 root root 4096 Feb 8 06:18 ..
182 | -rw-r--r-- 1 root root 0 Feb 8 06:20 hoge
183 |
184 | # exit
185 | ```
186 |
187 | #### ファイルの確認
188 |
189 | `app`内に`hoge`が存在すれば`docker`にマウントが成功している
190 |
191 | ```
192 | $ ls -la app
193 | total 0
194 | drwxr-xr-x 3 miurahironori staff 96 2 8 15:29 .
195 | drwxr-xr-x 4 miurahironori staff 128 2 8 15:28 ..
196 | -rw-r--r-- 1 miurahironori staff 0 2 8 15:29 hoge
197 | ```
198 |
199 | ### 1.2 VSCode から接続し開発
200 |
201 | #### 拡張機能のインストール
202 |
203 | VSCode -> 拡張機能 -> [Remote Development](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.vscode-remote-extensionpack)をインストール
204 |
205 | #### 接続
206 |
207 | `sample:1`の横の`+`を押下後、ディレクトリ(フォルダー)の入力を聞かれた場合は`/app`を入力
208 | 
209 |
210 | 新しいウインドウで作成した Docker 環境に接続し開発ができる状態であること
211 | 
212 |
213 | fuga と言うファイルを追加し`Docker-DockerCompose-Training`から確認
214 |
215 | ```
216 | $ ls -la app
217 | total 0
218 | drwxr-xr-x 3 miurahironori staff 96 2 8 15:29 .
219 | drwxr-xr-x 4 miurahironori staff 128 2 8 15:28 ..
220 | -rw-r--r-- 1 miurahironori staff 0 2 8 15:29 hoge
221 | -rw-r--r-- 1 miurahironori staff 0 2 8 15:33 fuga
222 | ```
223 |
224 | ### 1.3 Docker の基本的な操作
225 |
226 | #### Docker イメージの確認
227 |
228 | ```
229 | $ docker images
230 | REPOSITORY TAG IMAGE ID CREATED SIZE
231 | sample 1 43a2ae7ab3ee About an hour ago 936MB
232 | node latest 96e42e8537de 2 days ago 936MB
233 | ```
234 |
235 | #### build
236 |
237 | **build 済みなので実行しない**
238 |
239 | ~~初回は`Docker-DockerCompose-Training`から実行~~
240 |
241 | ```
242 | $ docker build --file=./docker/app/Dockerfile -t sample:1 .
243 | ```
244 |
245 | #### Docker コンテナの確認
246 |
247 | ```
248 | $ docker ps -a
249 | CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
250 | d621283c4a42 sample:1 "docker-entrypoint.s…" 16 minutes ago Up 16 minutes 0.0.0.0:3000->3000/tcp sample-1
251 | ```
252 |
253 | #### 新しい Docker コンテナの起動
254 |
255 | **実行済みなので実行しない**
256 |
257 | ~~初回は`Docker-DockerCompose-Training`から実行~~
258 |
259 | ```
260 | $ docker run -v `pwd`/app:/app -it -d --name sample-1 -p 3000:3000 sample:1
261 | ```
262 |
263 | #### Docker コンテナの停止
264 |
265 | `docker ps -a`で確認した`CONTAINER ID`を指定
266 |
267 | ```
268 | $ docker stop d621283c4a42
269 | ```
270 |
271 | #### Docker コンテナの起動
272 |
273 | `docker ps -a`で確認した`CONTAINER ID`を指定
274 |
275 | ```
276 | $ docker start d621283c4a42
277 | ```
278 |
279 | #### Docker コンテナの削除
280 |
281 | 停止後`docker ps -a`で確認した`CONTAINER ID`を指定
282 |
283 | ```
284 | $ docker rm d621283c4a42
285 | ```
286 |
287 | 確認
288 |
289 | ```
290 | $ docker ps -a
291 | ```
292 |
293 | #### Docker イメージの削除
294 |
295 | `docker images`で確認した`IMAGE ID`を指定
296 |
297 | ```
298 | $ docker images
299 | REPOSITORY TAG IMAGE ID CREATED SIZE
300 | sample 1 43a2ae7ab3ee 2 hours ago 936MB
301 |
302 | $ docker rmi 43a2ae7ab3ee
303 | ```
304 |
305 | ### 掃除
306 |
307 | `app`内を掃除
308 |
309 | ```
310 | $ rm app/*
311 | ```
312 |
313 | コンテナから開いていた VSCode も閉じる
314 |
315 | ## 2. Docker Compose で環境構築
316 |
317 | ### React サンプルコードの準備
318 |
319 | ローカルの`Docker-DockerCompose-Training`直下から実行
320 |
321 | ```
322 | $ cd app
323 | $ npx create-react-app .
324 | ```
325 |
326 | ### docker-compose.yml の作成
327 |
328 | `Docker-DockerCompose-Training` 直下に[docker-compose.yml](./docker-compose.yml)を作成
329 |
330 | ```
331 | $ cat docker-compose.yml
332 | version: "3"
333 | services:
334 | app:
335 | build: docker/app
336 | tty: true
337 | volumes:
338 | - ./app:/app
339 | ports:
340 | - "3000:3000"
341 | user: node
342 | command: yarn start
343 | ```
344 |
345 | ### docker-compose up
346 |
347 | `Docker-DockerCompose-Training` 直下で実行
348 |
349 | ```
350 | $ docker-compose up
351 | ```
352 |
353 | `Compiled successfully!`となること
354 | 
355 |
356 | ブラウザに`http://localhost:3000`を入力しサンプルコードが表示されること(確認後、Ctrl+C で停止できる、ただし今はこのまま動作させ続けること)
357 |
358 | 
359 |
360 | ### 2.1 Docker にマウントした app 配下の React のサンプルコードを用い開発
361 |
362 | `app/src/index.js`,`app/src/App.js`を編集し`hello`と表示されることを確認しましょう
363 |
364 | `index.js`
365 |
366 | ```
367 | import React from "react"
368 | import ReactDOM from "react-dom"
369 | import App from "./App"
370 |
371 | ReactDOM.render(
372 |
373 |
374 | ,
375 | document.getElementById("root")
376 | )
377 | ```
378 |
379 | `App.js`
380 |
381 | ```
382 | import React from 'react'
383 |
384 | const App = () => {
385 | return (
386 | <>
387 | hello
388 | >
389 | );
390 | }
391 |
392 | export default App;
393 | ```
394 |
395 | 表示が変わらない場合は`docker-compose stop`後に`docker-compose start`をし確認する(少し時間が掛かる)
396 |
397 | ### 2.2 VSCode から接続し開発
398 |
399 | #### 接続
400 |
401 | リモートエクスプローラー -> 今回作成した Docker-Compose の環境を選択
402 |
403 | 
404 |
405 | フォルダを押下
406 | 
407 |
408 | `app`を指定し`OK`を押下(2 回)
409 | 
410 |
411 | #### 開発
412 |
413 | `app/src/App.js`を編集し`docker compose hello`と表示されることを確認しましょう
414 |
415 | ### 2.3 Docker Compose の基本的な操作
416 |
417 | #### 起動
418 |
419 | フォアグランドで起動(停止は Ctrl+C)
420 |
421 | ```
422 | $ docker-compose up
423 | ```
424 |
425 | バックグラウンドで起動
426 |
427 | ```
428 | $ docker-compose up
429 | ```
430 |
431 | #### 起動
432 |
433 | ```
434 | $ docker-compose start
435 | ```
436 |
437 | #### 停止
438 |
439 | ```
440 | $ docker-compose stop
441 | ```
442 |
443 | #### 確認
444 |
445 | ```
446 | $ docker-compose ps
447 | Name Command State Ports
448 | -----------------------------------------------------------------------------------------------------
449 | docker-dockercompose-training_app_1 docker-entrypoint.sh yarn ... Up 0.0.0.0:3000->3000/tcp
450 | ```
451 |
--------------------------------------------------------------------------------
/recipe-1/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "3"
2 | services:
3 | app:
4 | build: docker/app
5 | tty: true
6 | volumes:
7 | - ./app:/app
8 | ports:
9 | - "3000:3000"
10 | user: node
11 | command: yarn start
12 |
--------------------------------------------------------------------------------
/recipe-1/docker/app/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:latest
2 |
3 | ENV APP_PATH=/app
4 | RUN mkdir $APP_PATH
5 | WORKDIR $APP_PATH
--------------------------------------------------------------------------------
/recipe-1/images/docker-01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hironomiu/Docker-DockerCompose-Training/2c88c7b56858ae4740a856de776b5f6a594ed343/recipe-1/images/docker-01.png
--------------------------------------------------------------------------------
/recipe-1/images/docker-02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hironomiu/Docker-DockerCompose-Training/2c88c7b56858ae4740a856de776b5f6a594ed343/recipe-1/images/docker-02.png
--------------------------------------------------------------------------------
/recipe-1/images/docker-compose-01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hironomiu/Docker-DockerCompose-Training/2c88c7b56858ae4740a856de776b5f6a594ed343/recipe-1/images/docker-compose-01.png
--------------------------------------------------------------------------------
/recipe-1/images/docker-compose-02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hironomiu/Docker-DockerCompose-Training/2c88c7b56858ae4740a856de776b5f6a594ed343/recipe-1/images/docker-compose-02.png
--------------------------------------------------------------------------------
/recipe-1/images/docker-compose-03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hironomiu/Docker-DockerCompose-Training/2c88c7b56858ae4740a856de776b5f6a594ed343/recipe-1/images/docker-compose-03.png
--------------------------------------------------------------------------------
/recipe-1/images/docker-compose-04.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hironomiu/Docker-DockerCompose-Training/2c88c7b56858ae4740a856de776b5f6a594ed343/recipe-1/images/docker-compose-04.png
--------------------------------------------------------------------------------
/recipe-1/images/docker-compose-05.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hironomiu/Docker-DockerCompose-Training/2c88c7b56858ae4740a856de776b5f6a594ed343/recipe-1/images/docker-compose-05.png
--------------------------------------------------------------------------------
/recipe-1/images/docker-compose-06.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hironomiu/Docker-DockerCompose-Training/2c88c7b56858ae4740a856de776b5f6a594ed343/recipe-1/images/docker-compose-06.png
--------------------------------------------------------------------------------
/recipe-2-0/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "3"
2 | services:
3 | react-app:
4 | build: docker/react-app
5 | tty: true
6 | volumes:
7 | - react-app:/react-app
8 | ports:
9 | - "3000:3000"
10 | user: root
11 |
12 | express-app:
13 | build: docker/express-app
14 | tty: true
15 | volumes:
16 | - express-app:/express-app
17 | ports:
18 | - "5000:5000"
19 | user: root
20 | volumes:
21 | react-app:
22 | express-app:
23 |
--------------------------------------------------------------------------------
/recipe-2-0/docker/express-app/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:latest
2 |
3 | ENV APP_PATH=/express-app
4 | RUN mkdir $APP_PATH
5 | WORKDIR $APP_PATH
6 |
7 | RUN npm init -y
8 | RUN npm install -y express cors pug
--------------------------------------------------------------------------------
/recipe-2-0/docker/react-app/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:latest
2 |
3 | ENV APP_PATH=/react-app
4 | RUN mkdir $APP_PATH
5 | WORKDIR $APP_PATH
6 |
7 | RUN npx create-react-app .
8 |
9 |
--------------------------------------------------------------------------------
/recipe-2-0/images/docker-01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hironomiu/Docker-DockerCompose-Training/2c88c7b56858ae4740a856de776b5f6a594ed343/recipe-2-0/images/docker-01.png
--------------------------------------------------------------------------------
/recipe-2-0/images/docker-02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hironomiu/Docker-DockerCompose-Training/2c88c7b56858ae4740a856de776b5f6a594ed343/recipe-2-0/images/docker-02.png
--------------------------------------------------------------------------------
/recipe-2-0/images/docker-03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hironomiu/Docker-DockerCompose-Training/2c88c7b56858ae4740a856de776b5f6a594ed343/recipe-2-0/images/docker-03.png
--------------------------------------------------------------------------------
/recipe-2-0/images/docker-04.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hironomiu/Docker-DockerCompose-Training/2c88c7b56858ae4740a856de776b5f6a594ed343/recipe-2-0/images/docker-04.png
--------------------------------------------------------------------------------
/recipe-2-0/images/docker-05.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hironomiu/Docker-DockerCompose-Training/2c88c7b56858ae4740a856de776b5f6a594ed343/recipe-2-0/images/docker-05.png
--------------------------------------------------------------------------------
/recipe-2-0/images/docker-06.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hironomiu/Docker-DockerCompose-Training/2c88c7b56858ae4740a856de776b5f6a594ed343/recipe-2-0/images/docker-06.png
--------------------------------------------------------------------------------
/recipe-2-0/images/docker-07.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hironomiu/Docker-DockerCompose-Training/2c88c7b56858ae4740a856de776b5f6a594ed343/recipe-2-0/images/docker-07.png
--------------------------------------------------------------------------------
/recipe-2-0/images/docker-08.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hironomiu/Docker-DockerCompose-Training/2c88c7b56858ae4740a856de776b5f6a594ed343/recipe-2-0/images/docker-08.png
--------------------------------------------------------------------------------
/recipe-2-0/images/docker-09.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hironomiu/Docker-DockerCompose-Training/2c88c7b56858ae4740a856de776b5f6a594ed343/recipe-2-0/images/docker-09.png
--------------------------------------------------------------------------------
/recipe-2-0/images/docker-10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hironomiu/Docker-DockerCompose-Training/2c88c7b56858ae4740a856de776b5f6a594ed343/recipe-2-0/images/docker-10.png
--------------------------------------------------------------------------------
/recipe-2-0/images/docker-compose-01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hironomiu/Docker-DockerCompose-Training/2c88c7b56858ae4740a856de776b5f6a594ed343/recipe-2-0/images/docker-compose-01.png
--------------------------------------------------------------------------------
/recipe-2-0/images/docker-compose-02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hironomiu/Docker-DockerCompose-Training/2c88c7b56858ae4740a856de776b5f6a594ed343/recipe-2-0/images/docker-compose-02.png
--------------------------------------------------------------------------------
/recipe-2-0/images/docker-compose-03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hironomiu/Docker-DockerCompose-Training/2c88c7b56858ae4740a856de776b5f6a594ed343/recipe-2-0/images/docker-compose-03.png
--------------------------------------------------------------------------------
/recipe-2-0/images/docker-compose-04.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hironomiu/Docker-DockerCompose-Training/2c88c7b56858ae4740a856de776b5f6a594ed343/recipe-2-0/images/docker-compose-04.png
--------------------------------------------------------------------------------
/recipe-2-0/images/docker-compose-05.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hironomiu/Docker-DockerCompose-Training/2c88c7b56858ae4740a856de776b5f6a594ed343/recipe-2-0/images/docker-compose-05.png
--------------------------------------------------------------------------------
/recipe-2-0/images/docker-compose-06.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hironomiu/Docker-DockerCompose-Training/2c88c7b56858ae4740a856de776b5f6a594ed343/recipe-2-0/images/docker-compose-06.png
--------------------------------------------------------------------------------
/recipe-2-0/sample/express-app/example.js:
--------------------------------------------------------------------------------
1 | const express = require('express')
2 | const cors = require('cors')
3 | const http = require('http')
4 |
5 | const app = express()
6 | const server = http.createServer(app)
7 |
8 | app.set('view engine', 'pug')
9 |
10 | app.use(
11 | express.urlencoded({
12 | extended: true,
13 | })
14 | )
15 | app.use(express.json())
16 |
17 | app.use(
18 | cors({
19 | origin: 'http://localhost:3000',
20 | credentials: true,
21 | optionsSuccessStatus: 200,
22 | })
23 | )
24 |
25 | app.get('/', (req, res) => {
26 | res.render('index', {
27 | title: 'Hey',
28 | message: 'Hello there!',
29 | })
30 | })
31 |
32 | app.get('/api/users', (req, res) => {
33 | res.json([{ name: 'Bob' }, { name: '花子' }, { name: '太郎' }])
34 | })
35 |
36 | app.get('/api/users/:id', (req, res) => {
37 | res.json({ name: `No.${req.params.id} is Bob` })
38 | })
39 |
40 | app.post('/api/users', (req, res) => {
41 | res.json({
42 | message: 'POST ' + req.body.name + ' Successful',
43 | })
44 | })
45 |
46 | server.listen(5000, () => {
47 | console.log('listening on *:5000')
48 | })
49 |
--------------------------------------------------------------------------------
/recipe-2-0/sample/express-app/index.js:
--------------------------------------------------------------------------------
1 | const express = require('express')
2 | const cors = require('cors')
3 | const http = require('http')
4 |
5 | const app = express()
6 | const server = http.createServer(app)
7 |
8 | app.set('view engine', 'pug')
9 |
10 | app.use(
11 | cors({
12 | origin: 'http://localhost:3000',
13 | credentials: true,
14 | optionsSuccessStatus: 200,
15 | })
16 | )
17 |
18 | app.get('/', (req, res) => {
19 | res.render('index', {
20 | title: 'Hey',
21 | message: 'Hello there!',
22 | })
23 | })
24 |
25 | app.get('/api/users', (req, res) => {
26 | res.json([{ name: 'Bob' }, { name: '花子' }, { name: '太郎' }])
27 | })
28 |
29 | app.get('/api/users/:id', (req, res) => {
30 | res.json({ name: `No.${req.params.id} is Bob` })
31 | })
32 |
33 | server.listen(5000, () => {
34 | console.log('listening on *:5000')
35 | })
36 |
--------------------------------------------------------------------------------
/recipe-2-0/sample/express-app/views/index.pug:
--------------------------------------------------------------------------------
1 | html
2 | head
3 | title= title
4 | body
5 | h1= message
--------------------------------------------------------------------------------
/recipe-2-0/sample/react-app/Index.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import ReactDOM from "react-dom"
3 | import App from "./components/App"
4 |
5 | ReactDOM.render(
6 |
7 |
8 | ,
9 | document.getElementById("root")
10 | )
11 |
--------------------------------------------------------------------------------
/recipe-2-0/sample/react-app/components/App.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react'
2 |
3 | const App = () => {
4 | const [users, setUsers] = useState([{ name: '一郎' }, { name: 'Mike' }])
5 | useEffect(() => {
6 | fetch('http://localhost:5000/api/users')
7 | .then((res) => res.json())
8 | .then((data) => setUsers([...data]))
9 | })
10 | return (
11 |
12 | {users.map((user, index) => (
13 |
{user.name}
14 | ))}
15 |
16 | )
17 | }
18 |
19 | export default App
20 |
--------------------------------------------------------------------------------
/recipe-2-0/sample/react-app/components/Example.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react'
2 |
3 | const App = () => {
4 | const [name, setName] = useState('hoge')
5 | const [users, setUsers] = useState([])
6 | useEffect(() => {
7 | fetch('http://localhost:5000/api/users')
8 | .then((res) => res.json())
9 | .then((data) => setUsers([...data]))
10 | }, [])
11 | return (
12 |
13 | {users.map((user, index) => (
14 |
{user.name}
15 | ))}
16 |
setName(e.target.value)}
20 | />
21 |
44 |
45 | )
46 | }
47 |
48 | export default App
49 |
--------------------------------------------------------------------------------
/recipe-2-1/README.md:
--------------------------------------------------------------------------------
1 | # recipe-2.1 Docker-React-Express-MySQL
2 |
3 | recipe-2-0 に MySQL を追加、express-app を router で書き直し
4 |
5 | ## 前提
6 |
7 | recipe-2-0 に追加する形で実装(`Docker-React-Express`ディレクトリ配下で実装する)
8 |
9 | ## MySQL
10 |
11 | `docker`配下に`mysql`ディレクトリを作成し Dockerfile を配置する
12 |
13 | ```
14 | FROM mysql:latest
15 | ADD ./conf.d/my.cnf /etc/mysql/conf.d/my.cnf
16 | ```
17 |
18 | `docker/mysql`配下に`conf.d`ディレクトリを作成し my.cnf を配置する
19 |
20 | ```
21 | [mysqld]
22 | innodb-buffer-pool-size=128M
23 | default-authentication-plugin=mysql_native_password
24 | ```
25 |
26 | ## express-app/Dockerfile
27 |
28 | `mysql2`のインストールを追記する
29 |
30 | ```
31 | FROM node:latest
32 |
33 | ENV APP_PATH=/express-app
34 | RUN mkdir $APP_PATH
35 | WORKDIR $APP_PATH
36 |
37 | RUN npm init -y
38 | RUN npm install -y express cors mysql2 pug
39 | ```
40 |
41 | ## docker-compose.yml
42 |
43 | MySQL の設定を追加しサーバサイド(`express-ap`)から参照できるよう追記する
44 |
45 | ```
46 | version: '3'
47 | services:
48 | react-app:
49 | build: docker/react-app
50 | tty: true
51 | volumes:
52 | - react-app:/react-app
53 | ports:
54 | - '3000:3000'
55 | user: root
56 | express-app:
57 | build: docker/express-app
58 | tty: true
59 | volumes:
60 | - express-app:/express-app
61 | ports:
62 | - '5000:5000'
63 | user: root
64 | depends_on:
65 | - db
66 | db:
67 | build: docker/mysql
68 | tty: true
69 | volumes:
70 | - mysql:/var/lib/mysql
71 | environment:
72 | MYSQL_DATABASE: test
73 | MYSQL_ROOT_PASSWORD: mysql
74 | MYSQL_USER: appuser
75 | MYSQL_PASSWORD: mysql
76 | expose:
77 | - '3306'
78 | ports:
79 | - '3307:3306'
80 | volumes:
81 | react-app:
82 | express-app:
83 | mysql:
84 | ```
85 |
86 | ## run
87 |
88 | ```
89 | $ docker-compose up --build -d
90 | ```
91 |
92 | ## express-app
93 |
94 | VSCode から接続しサーバ(`index.js`)を起動する
95 |
96 | ```
97 | node index.js
98 | ```
99 |
100 | ## react-app
101 |
102 | VSCode から接続しアプリを起動する
103 |
104 | ```
105 | yarn start
106 | ```
107 |
108 | ## express-app の修正
109 |
110 | `index.js`を MySQL に接続しレコードを取得しレスポンスで返すよう修正する
111 |
112 | ```
113 | import express from 'express'
114 | import http from 'http'
115 | import cors from 'cors'
116 | import { promisePool } from './config/db.js'
117 | import { ORIGIN_URL } from './config/index.js'
118 | import users from './router/api/users.js'
119 |
120 | const app = express()
121 | const server = http.createServer(app)
122 |
123 | app.set('view engine', 'pug')
124 |
125 | app.use('/static', express.static('static'))
126 |
127 | app.use(
128 | cors({
129 | origin: ORIGIN_URL,
130 | credentials: true,
131 | optionsSuccessStatus: 200,
132 | })
133 | )
134 |
135 | app.use(
136 | express.urlencoded({
137 | extended: true,
138 | })
139 | )
140 |
141 | app.use(express.json())
142 |
143 | app.use('/api/users', users)
144 | app.use('/api/users/:id', users)
145 |
146 | app.get('/', async (req, res) => {
147 | const [rows, fields] = await promisePool.query('select 1 as num')
148 | res.render('index', {
149 | title: 'Hey',
150 | message: `Hello there!num is ${rows[0].num}`,
151 | })
152 | })
153 |
154 | server.listen(5000, () => {
155 | console.log('listening 5000')
156 | })
157 | ```
158 |
159 | `express-app`配下に`router/api`ディレクトリを作成し`user.js`を配置する
160 |
161 | ```
162 | import express from 'express'
163 | import { promisePool } from '../../config/db.js'
164 |
165 | const users = express.Router()
166 |
167 | users
168 | .route('/')
169 | .get(async (req, res) => {
170 | const [rows] = await promisePool.query('select id,name from users')
171 | res.json(rows)
172 | })
173 | .post(async (req, res) => {
174 | const ret = await promisePool.query('insert into users(name) values(?)', [
175 | req.body.name,
176 | ])
177 | console.log(ret[0].insertId)
178 | res.json({ message: 'ok', insertId: ret[0].insertId })
179 | })
180 |
181 | users
182 | .route('/:id')
183 | .get(async (req, res) => {
184 | const [rows] = await promisePool.query(
185 | 'select name from users where id = ?',
186 | [req.params.id]
187 | )
188 | res.json({ name: rows[0].name })
189 | })
190 | .post(async (req, res) => {
191 | const ret = await promisePool.query(
192 | 'update users set name = ? where id = ?',
193 | [req.body.name, req.params.id]
194 | )
195 | // console.log(ret[0])
196 | res.json({ message: ret[0].info, insertId: ret[0].insertId })
197 | })
198 |
199 | export default users
200 | ```
201 |
202 | `express-app`配下に`config`ディレクトリを作成し`db.js`,`index.js`を配置する
203 |
204 | `db.js`
205 |
206 | ```
207 | import mysql from 'mysql2'
208 |
209 | const pool = mysql.createPool({
210 | connectionLimit: 5,
211 | host: 'db',
212 | user: 'appuser',
213 | password: 'mysql',
214 | database: 'test',
215 | })
216 |
217 | export const promisePool = pool.promise()
218 | ```
219 |
220 | `index.js`
221 |
222 | ```
223 | export const ORIGIN_URL = 'http://localhost:3000'
224 | ```
225 |
226 | ES6 で実装するため`package.json`に`"type": "module",`を`scripts`以下に追記する
227 |
228 | ```
229 | "scripts": {
230 | "test": "echo \"Error: no test specified\" && exit 1"
231 | },
232 | "type": "module",
233 | ```
234 |
235 | ## MySQL
236 |
237 | MySQL に接続し`test`DB に api 用のテーブルを作成する
238 |
239 | ```
240 | mysql -u root -p --port=3307 -h127.0.0.1
241 |
242 | mysql> use test
243 |
244 | ```
245 |
246 | 以下を実行
247 |
248 | ```
249 | create table users (
250 | id int auto_increment not null,
251 | name varchar(10) not null ,
252 | primary key(id)
253 | );
254 | ```
255 |
256 | サンプルデータを insert する
257 |
258 | ```
259 | insert into users(name) values('太郎'),('John'),('花子');
260 | ```
261 |
262 | exit
263 |
264 | ```
265 | mysql> exit
266 | ```
267 |
268 | ## サーバの再起動
269 |
270 | express-app の`index.js`が起動している場合 CTRL + C で停止し再度起動する
271 |
272 | ```
273 | node index.js
274 | ```
275 |
--------------------------------------------------------------------------------
/recipe-2-1/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 | services:
3 | react-app:
4 | build: docker/react-app
5 | tty: true
6 | volumes:
7 | - react-app:/react-app
8 | ports:
9 | - '3000:3000'
10 | user: root
11 | express-app:
12 | build: docker/express-app
13 | tty: true
14 | volumes:
15 | - express-app:/express-app
16 | ports:
17 | - '5000:5000'
18 | user: root
19 | depends_on:
20 | - db
21 | db:
22 | build: docker/mysql
23 | tty: true
24 | volumes:
25 | - mysql:/var/lib/mysql
26 | environment:
27 | MYSQL_DATABASE: test
28 | MYSQL_ROOT_PASSWORD: mysql
29 | MYSQL_USER: appuser
30 | MYSQL_PASSWORD: mysql
31 | expose:
32 | - '3306'
33 | ports:
34 | - '3307:3306'
35 | volumes:
36 | react-app:
37 | express-app:
38 | mysql:
39 |
--------------------------------------------------------------------------------
/recipe-2-1/docker/express-app/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:latest
2 |
3 | ENV APP_PATH=/express-app
4 | RUN mkdir $APP_PATH
5 | WORKDIR $APP_PATH
6 |
7 | RUN npm init -y
8 | RUN npm install -y express cors mysql2 pug
--------------------------------------------------------------------------------
/recipe-2-1/docker/mysql/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM mysql:latest
2 | ADD ./conf.d/my.cnf /etc/mysql/conf.d/my.cnf
--------------------------------------------------------------------------------
/recipe-2-1/docker/mysql/conf.d/my.cnf:
--------------------------------------------------------------------------------
1 | [mysqld]
2 | innodb-buffer-pool-size=128M
3 | default-authentication-plugin=mysql_native_password
--------------------------------------------------------------------------------
/recipe-2-1/docker/react-app/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:latest
2 |
3 | ENV APP_PATH=/react-app
4 | RUN mkdir $APP_PATH
5 | WORKDIR $APP_PATH
6 |
7 | RUN npx create-react-app .
--------------------------------------------------------------------------------
/recipe-2-1/sample/express-app/config/db.js:
--------------------------------------------------------------------------------
1 | import mysql from 'mysql2'
2 |
3 | const pool = mysql.createPool({
4 | connectionLimit: 5,
5 | host: 'db',
6 | user: 'appuser',
7 | password: 'mysql',
8 | database: 'test',
9 | })
10 |
11 | export const promisePool = pool.promise()
12 |
--------------------------------------------------------------------------------
/recipe-2-1/sample/express-app/config/index.js:
--------------------------------------------------------------------------------
1 | export const ORIGIN_URL = 'http://localhost:3000'
2 |
--------------------------------------------------------------------------------
/recipe-2-1/sample/express-app/index.js:
--------------------------------------------------------------------------------
1 | import express from 'express'
2 | import http from 'http'
3 | import cors from 'cors'
4 | import { promisePool } from './config/db.js'
5 | import { ORIGIN_URL } from './config/index.js'
6 | import users from './router/api/users.js'
7 |
8 | const app = express()
9 | const server = http.createServer(app)
10 |
11 | app.set('view engine', 'pug')
12 |
13 | app.use('/static', express.static('static'))
14 |
15 | app.use(
16 | cors({
17 | origin: ORIGIN_URL,
18 | credentials: true,
19 | optionsSuccessStatus: 200,
20 | })
21 | )
22 |
23 | app.use(
24 | express.urlencoded({
25 | extended: true,
26 | })
27 | )
28 |
29 | app.use(express.json())
30 |
31 | app.use('/api/users', users)
32 | app.use('/api/users/:id', users)
33 |
34 | app.get('/', async (_, res) => {
35 | const [rows, fields] = await promisePool.query('select 1 as num')
36 | res.render('index', {
37 | title: 'Hey',
38 | message: `Hello there!num is ${rows[0].num}`,
39 | })
40 | })
41 |
42 | server.listen(5000, () => {
43 | console.log('listening on *:5000')
44 | })
45 |
--------------------------------------------------------------------------------
/recipe-2-1/sample/express-app/router/api/users.js:
--------------------------------------------------------------------------------
1 | import express from 'express'
2 | import { promisePool } from '../../config/db.js'
3 |
4 | const users = express.Router()
5 |
6 | users
7 | .route('/')
8 | .get(async (req, res) => {
9 | const [rows] = await promisePool.query('select id,name from users')
10 | res.json(rows)
11 | })
12 | .post(async (req, res) => {
13 | const ret = await promisePool.query('insert into users(name) values(?)', [
14 | req.body.name,
15 | ])
16 | console.log(ret[0].insertId)
17 | res.json({ message: 'ok', insertId: ret[0].insertId })
18 | })
19 |
20 | users
21 | .route('/:id')
22 | .get(async (req, res) => {
23 | const [rows] = await promisePool.query(
24 | 'select name from users where id = ?',
25 | [req.params.id]
26 | )
27 | res.json({ name: rows[0].name })
28 | })
29 | .post(async (req, res) => {
30 | const ret = await promisePool.query(
31 | 'update users set name = ? where id = ?',
32 | [req.body.name, req.params.id]
33 | )
34 | // console.log(ret[0])
35 | res.json({ message: ret[0].info, insertId: ret[0].insertId })
36 | })
37 |
38 | export default users
39 |
--------------------------------------------------------------------------------
/recipe-2-1/sample/express-app/views/index.pug:
--------------------------------------------------------------------------------
1 | html
2 | head
3 | title= title
4 | body
5 | h1= message
6 |
--------------------------------------------------------------------------------
/recipe-2-1/sample/react-app/Index.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import ReactDOM from "react-dom"
3 | import App from "./components/App"
4 |
5 | ReactDOM.render(
6 |
7 |
8 | ,
9 | document.getElementById("root")
10 | )
11 |
--------------------------------------------------------------------------------
/recipe-2-1/sample/react-app/components/App.js:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react'
2 |
3 | const API_URL = 'http://localhost:5000/api/users'
4 |
5 | const userEntry = async (name, setUsers, setName) => {
6 | const bodyData = JSON.stringify({ name: name })
7 |
8 | const res = await fetch(API_URL, {
9 | method: 'POST',
10 | mode: 'cors',
11 | cache: 'no-cache',
12 | headers: {
13 | 'Content-Type': 'application/json',
14 | },
15 | body: bodyData,
16 | })
17 | const data = await res.json()
18 |
19 | setUsers((users) => [...users, { id: data.insertId, name: name }])
20 | setName('')
21 | }
22 |
23 | const getUsers = async (setUsers) => {
24 | const res = await fetch(API_URL)
25 | const json = await res.json()
26 | setUsers([...json])
27 | }
28 |
29 | const App = () => {
30 | const [name, setName] = useState('bob')
31 | const [users, setUsers] = useState([])
32 | const [buttonIs, setButtonIs] = useState(true)
33 |
34 | useEffect(() => {
35 | getUsers(setUsers)
36 | }, [])
37 |
38 | useEffect(() => {
39 | if (name.length !== 0) {
40 | setButtonIs(false)
41 | } else {
42 | setButtonIs(true)
43 | }
44 | }, [name])
45 |
46 | return (
47 |
48 | {users.map((user, index) => (
49 |
50 | {user.id}:{user.name}
51 |
52 | ))}
53 |
{
56 | setName(e.target.value)
57 | if (name.length !== 0) {
58 | setButtonIs(false)
59 | }
60 | }}
61 | value={name}
62 | />
63 |
72 |
73 | )
74 | }
75 |
76 | export default App
77 |
--------------------------------------------------------------------------------
/recipe-2-2/README.md:
--------------------------------------------------------------------------------
1 | # recipe-2-2 Docker-React-Express-MySQL-JWT-COOKIE-CSRF
2 |
3 | recipe-2-1 に サーバサイド jwt,cookie,csrf,validator を追加、フロント redux-toolkit tailwind を追加
4 |
5 | ## 前提
6 |
7 | recipe-2-0,recipe-2-1 に追加だが、`create-react-app`のテンプレートを変更するため新規で作成する(`Docker-React-Express`ディレクトリ配下で実装する)
8 |
9 | ```
10 | mkdir Docker-React-Express
11 | cd Docker-React-Express
12 | ```
13 |
14 | ## docker/express-app/Dockerfile
15 |
16 | `Dockerfile`に`jsonwebtoken`,`cookie-parser`,`csurf`,`bcrypt`,`express-validator`を追記
17 |
18 | ```
19 | FROM node:latest
20 |
21 | ENV APP_PATH=/express-app
22 | RUN mkdir $APP_PATH
23 | WORKDIR $APP_PATH
24 |
25 | RUN npm init -y
26 | RUN npm install -y express cors mysql2 pug jsonwebtoken cookie-parser csurf bcrypt express-validator
27 |
28 | ```
29 |
30 | ## docker/react-app/Dockerfile
31 |
32 | recipe-2-1 から テンプレートを redux-toolkit に変更し使うため template redux の利用に Dockerfile を修正(`yarn start`で`node:latest`だとエラーになる場合`FROM node:16.0`を指定し build し直す)、Tailwind 周りのインストールを追加
33 |
34 | ```
35 | FROM node:latest
36 |
37 | ENV APP_PATH=/react-app
38 | RUN mkdir $APP_PATH
39 | WORKDIR $APP_PATH
40 |
41 | RUN npx create-react-app . --template redux
42 | RUN yarn add @headlessui/react
43 | RUN yarn add @heroicons/react
44 | RUN yarn add -D tailwindcss@npm:@tailwindcss/postcss7-compat postcss@^7 autoprefixer@^9
45 | RUN yarn add @craco/craco
46 | ```
47 |
48 | ## docker/mysql/Dockerfile
49 |
50 | recipe-2-1 と同じ
51 |
52 | ## docker/mysql/conf.d/my.cnf
53 |
54 | recipe-2-1 と同じ
55 |
56 | ## docker-compose.yml
57 |
58 | recipe-2-1 と同じ
59 |
60 | ## up
61 |
62 | ```
63 | $ docker-compose up --build -d
64 | ```
65 |
66 | `jwt.config.js`の`secret`は以下で作成
67 |
68 | ```
69 | $ node -e "console.log(require('jsonwebtoken').sign({username:'hoge'},'my_secret'))"
70 | ```
71 |
72 | ## MySQL
73 |
74 | recipe-2-1 と以下同じ(email,password を追加)
75 |
76 | MySQL に接続し`test`DB に api 用のテーブルを作成する
77 |
78 | ```
79 | mysql -u root -p --port=3307 -h127.0.0.1
80 |
81 | mysql> use test
82 |
83 | ```
84 |
85 | `users`テーブルの削除&作成
86 |
87 | ```
88 | drop table users;
89 | create table users (
90 | id int auto_increment not null,
91 | name varchar(100) not null ,
92 | email varchar(100) not null,
93 | password varchar(100) not null,
94 | primary key(id),
95 | unique (email)
96 | );
97 |
98 | drop table task_status;
99 | create table task_status (
100 | id int auto_increment not null,
101 | name varchar(100) not null,
102 | primary key(id),
103 | unique (name)
104 | );
105 |
106 | drop table tasks;
107 | create table tasks (
108 | id int auto_increment not null,
109 | title varchar(100) not null,
110 | task text not null,
111 | status int not null,
112 | user_id int not null,
113 | primary key(id),
114 | FOREIGN KEY (status) REFERENCES task_status (id)
115 | );
116 |
117 | ```
118 |
119 | `users`,`task_status`,`tasks`テーブルのサンプルデータを insert (`bcrypt` で hash 化してある、パスワードは全て`abcd`)
120 |
121 | ```
122 | truncate table users;
123 | insert into users(id,name,email,password) values
124 | (1,'太郎','taro@example.com','$2b$10$iFCxa4wOsuZhklYp00bnCuk0sBJxGOU.e4YnfqvoDEyIk1C1rrd0K'),
125 | (2,'John','john@example.com','$2b$10$8W1a6GfBsmn/gY8jhXjGbOCQwcWfF/PeI5O07ONakuhX9bYIZNe82'),
126 | (3,'花子','hanako@example.com','$2b$10$TXCGCYDpn6p35Csz5UyoA.UHJ9SkE3Q7JP6lRO9ZgMaXuNwEo.wWW');
127 |
128 | truncate table task_status;
129 | insert into task_status(id,name) values
130 | (1,'未着手'),(2,'着手中'),(3,'完了');
131 |
132 | truncate table tasks;
133 | insert into tasks(id,title,task,status,user_id) values
134 | (1,'太郎 タスク1','タスク1の詳細',2,1),
135 | (2,'太郎 タスク2','タスク2の詳細',1,1),
136 | (3,'太郎 タスク3','タスク3の詳細',3,1),
137 | (4,'太郎 タスク3','タスク3の詳細',1,1),
138 | (5,'John タスク1','タスク1の詳細',1,2),
139 | (6,'John タスク2','タスク2の詳細',1,2);
140 |
141 |
142 | ```
143 |
144 | exit
145 |
146 | ```
147 | mysql> exit
148 | ```
149 |
150 | ## express-app アプリ
151 |
152 | [sample/espress-app](./sample/express-app)以下を`express-app`に記述
153 |
154 | ### Memo
155 |
156 | ```
157 | mkdir api config middlewares views
158 | touch api/csrfToken.js api/login.js api/logout.js api/users.js api/tasks.js
159 | touch config/db.js config/index.js config/jwt.config.js
160 | touch middlewares/validator.js middlewares/verifyToken.js
161 | touch views/index.pug
162 | touch index.js
163 | ```
164 |
165 | ### Run
166 |
167 | ```
168 | node index.js
169 | ```
170 |
171 | ## react-app アプリ
172 |
173 | `package.json`の scripts を`craco`で構成する
174 |
175 | before
176 |
177 | ```
178 | "start": "react-scripts start",
179 | "build": "react-scripts build",
180 | "test": "react-scripts test",
181 | ```
182 |
183 | after(eject は削除)
184 |
185 | ```
186 | "start": "craco start",
187 | "build": "craco build",
188 | "test": "craco test"
189 | ```
190 |
191 | `craco.config.js`を作成(touch ではなく VSCode からファイル作成でも良い)
192 |
193 | ```
194 | touch craco.config.js
195 | ```
196 |
197 | 作成した`craco.config.js`に以下を記述
198 |
199 | ```
200 | module.exports = {
201 | style: {
202 | postcss: {
203 | plugins: [require('tailwindcss'), require('autoprefixer')],
204 | },
205 | },
206 | }
207 | ```
208 |
209 | tailwind の初期化
210 |
211 | ```
212 | npx tailwindcss init -p
213 | ```
214 |
215 | `tailwind.config.js`の purge を修正
216 |
217 | ```
218 | - purge: [],
219 |
220 | + purge: ['./src/**/*.{js,jsx,ts,tsx}', './public/index.html'],
221 | ```
222 |
223 | `./src/index.css` を tailwind を利用する設定に修正(以下の 3 行に全てを書き換え)
224 |
225 | ```
226 | @tailwind base;
227 | @tailwind components;
228 | @tailwind utilities;
229 | ```
230 |
231 | [sample/react-app](./sample/react-app)以下を`react-app`に記述
232 |
233 | ### Run
234 |
235 | ```
236 | yarn start
237 | ```
238 |
239 | ### Memo
240 |
241 | ```
242 | mkdir src/app src/components src/config src/features/auth src/features/tasks
243 | touch src/app/store.js src/components/Footer.js src/components/Header.js src/components/Layout.js src/components/Login.js src/components/Main.js src/components/Modal.js src/components/SignUp.js src/components/Task.js src/components/TaskDeleteModal.js src/components/TaskInsertModal.js src/components/TaskUpdateModal.js src/config/index.js src/features/auth/authSlice.js src/features/tasks/tasksSlice.js
244 | ```
245 |
246 | ## 掃除
247 |
248 | ```
249 | docker-compose stop
250 | docker-compose rm
251 |
252 | docker image rm docker-react-express_react-app
253 | docker image rm docker-react-express_express-app
254 | docker image rm docker-react-express_db
255 | docker volume rm docker-react-express_react-app
256 | docker volume rm docker-react-express_express-app
257 | docker volume rm docker-react-express_mysql
258 | ```
259 |
--------------------------------------------------------------------------------
/recipe-2-2/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 | services:
3 | react-app:
4 | build: docker/react-app
5 | tty: true
6 | volumes:
7 | - react-app:/react-app
8 | ports:
9 | - '3000:3000'
10 | user: root
11 | express-app:
12 | build: docker/express-app
13 | tty: true
14 | volumes:
15 | - express-app:/express-app
16 | ports:
17 | - '5000:5000'
18 | user: root
19 | depends_on:
20 | - db
21 | db:
22 | build: docker/mysql
23 | tty: true
24 | volumes:
25 | - mysql:/var/lib/mysql
26 | environment:
27 | MYSQL_DATABASE: test
28 | MYSQL_ROOT_PASSWORD: mysql
29 | MYSQL_USER: appuser
30 | MYSQL_PASSWORD: mysql
31 | expose:
32 | - '3306'
33 | ports:
34 | - '3307:3306'
35 | volumes:
36 | react-app:
37 | express-app:
38 | mysql:
39 |
--------------------------------------------------------------------------------
/recipe-2-2/docker/express-app/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:latest
2 |
3 | ENV APP_PATH=/express-app
4 | RUN mkdir $APP_PATH
5 | WORKDIR $APP_PATH
6 |
7 | RUN npm init -y
8 | RUN npm install -y express cors mysql2 pug jsonwebtoken cookie-parser csurf bcrypt express-validator dotenv
--------------------------------------------------------------------------------
/recipe-2-2/docker/mysql/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM mysql:latest
2 | ADD ./conf.d/my.cnf /etc/mysql/conf.d/my.cnf
--------------------------------------------------------------------------------
/recipe-2-2/docker/mysql/conf.d/my.cnf:
--------------------------------------------------------------------------------
1 | [mysqld]
2 | innodb-buffer-pool-size=128M
3 | default-authentication-plugin=mysql_native_password
--------------------------------------------------------------------------------
/recipe-2-2/docker/react-app/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:latest
2 |
3 | ENV APP_PATH=/react-app
4 | RUN mkdir $APP_PATH
5 | WORKDIR $APP_PATH
6 |
7 | RUN npx create-react-app . --template redux
8 | RUN yarn add @headlessui/react
9 | RUN yarn add @heroicons/react
10 | RUN yarn add -D tailwindcss@npm:@tailwindcss/postcss7-compat postcss@^7 autoprefixer@^9
11 | RUN yarn add @craco/craco
--------------------------------------------------------------------------------
/recipe-2-2/sample/express-app/api/csrfToken.js:
--------------------------------------------------------------------------------
1 | const router = require('express').Router()
2 |
3 | router.get('/', (req, res) => {
4 | const csrfToken = req.csrfToken()
5 | res.json({ csrfToken: csrfToken })
6 | })
7 |
8 | module.exports = router
9 |
--------------------------------------------------------------------------------
/recipe-2-2/sample/express-app/api/login.js:
--------------------------------------------------------------------------------
1 | const router = require('express').Router()
2 | const jwt = require('jsonwebtoken')
3 | const config = require('../config/jwt.config')
4 | const promisePool = require('../config/db.js')
5 | const bcrypt = require('bcrypt')
6 | const {
7 | validator,
8 | checkEmailIsEmpty,
9 | checkEmailIsEmail,
10 | checkPasswordIsEmpty,
11 | } = require('../middlewares/validator')
12 | const isNotEmailPassWordErrorMessage = {
13 | isSuccess: false,
14 | message: 'ユーザーIDまたはパスワードが違います。',
15 | }
16 | const authErrorMessage = {
17 | isSuccess: false,
18 | message: 'トークンの認証に失敗しました。',
19 | }
20 | const isNotTokenErrorMessage = {
21 | isSuccess: false,
22 | message: 'トークンがありません。',
23 | }
24 |
25 | router
26 | .route('/')
27 | .get((req, res) => {
28 | const authHeader = req.headers.authorization
29 | const token = req.cookies.token
30 | ? req.cookies.token
31 | : authHeader && authHeader.split(' ')[1]
32 | if (token) {
33 | jwt.verify(token, config.jwt.secret, (error, _) => {
34 | if (error) {
35 | console.log('authErrorMessage')
36 | return res.status(200).send(authErrorMessage)
37 | }
38 | console.log('success')
39 | return res.status(200).send({
40 | isSuccess: true,
41 | message: 'success',
42 | token: token,
43 | })
44 | })
45 | } else {
46 | console.log(isNotTokenErrorMessage)
47 | return res.status(200).send(isNotTokenErrorMessage)
48 | }
49 | })
50 | .post(
51 | [checkEmailIsEmpty, checkEmailIsEmail, checkPasswordIsEmpty],
52 | validator,
53 | async (req, res) => {
54 | console.log(req.body)
55 | try {
56 | const [rows, fields] = await promisePool.query(
57 | 'select id,name,email,password from users where email = ?',
58 | [req.body.email]
59 | )
60 |
61 | if (rows.length === 0) return res.json(isNotEmailPassWordErrorMessage)
62 |
63 | const ret = await new Promise((resolve) =>
64 | bcrypt.compare(
65 | req.body.password,
66 | rows[0].password.toString(),
67 | (err, isValid) => resolve(isValid)
68 | )
69 | )
70 |
71 | if (req.body.email === rows[0].email && ret) {
72 | const payload = {
73 | id: rows[0].id,
74 | email: req.body.email,
75 | }
76 | const token = jwt.sign(payload, config.jwt.secret, config.jwt.options)
77 | res.cookie('token', token, {
78 | httpOnly: true,
79 | domain: 'localhost',
80 | path: '/',
81 | sameSite: 'none',
82 | secure: true,
83 | })
84 |
85 | return res.json({
86 | isSuccess: true,
87 | token: token,
88 | })
89 | }
90 | } catch (err) {
91 | return res.json({ isSuccess: false, message: 'データベースエラー' })
92 | }
93 | return res.json(isNotEmailPassWordErrorMessage)
94 | }
95 | )
96 |
97 | module.exports = router
98 |
--------------------------------------------------------------------------------
/recipe-2-2/sample/express-app/api/logout.js:
--------------------------------------------------------------------------------
1 | const router = require('express').Router()
2 | const jwt = require('jsonwebtoken')
3 | const config = require('../config/jwt.config')
4 | const verifyToken = require('../middlewares/verifyToken')
5 |
6 | router.post('/', verifyToken, (req, res) => {
7 | res.clearCookie('token')
8 | res.json({
9 | isSuccess: true,
10 | token: '',
11 | })
12 | })
13 | module.exports = router
14 |
--------------------------------------------------------------------------------
/recipe-2-2/sample/express-app/api/tasks.js:
--------------------------------------------------------------------------------
1 | const router = require('express').Router()
2 | const verifyToken = require('../middlewares/verifyToken')
3 | const promisePool = require('../config/db')
4 | const {
5 | validator,
6 | checkTasksTitleIsEmpty,
7 | checkTasksTaskIsEmpty,
8 | checkTasksStatusIsEmpty,
9 | } = require('../middlewares/validator')
10 |
11 | router
12 | .route('/')
13 | .get(verifyToken, async (req, res) => {
14 | const [rows, fields] = await promisePool.query(
15 | 'select a.id,a.title,a.task,a.status,b.name as status_name from tasks a inner join task_status b on a.status = b.id where a.user_id = ?',
16 | [req.decoded.id]
17 | )
18 | return res.json(rows)
19 | })
20 | .post(
21 | [checkTasksTitleIsEmpty, checkTasksTaskIsEmpty, checkTasksStatusIsEmpty],
22 | validator,
23 | verifyToken,
24 | async (req, res) => {
25 | try {
26 | const ret = await promisePool.query(
27 | 'insert into tasks(title,task,status,user_id) values(?,?,?,?)',
28 | [
29 | req.body.task.title,
30 | req.body.task.task,
31 | req.body.task.status,
32 | req.decoded.id,
33 | ]
34 | )
35 | return res.json({ isSuccess: true, message: 'ok' })
36 | } catch (err) {
37 | console.log(err)
38 | return res.json({ isSuccess: false, message: 'INSERT ERROR' })
39 | }
40 | }
41 | )
42 |
43 | router
44 | .route('/:id')
45 | .delete(verifyToken, async (req, res) => {
46 | // 自分のレコード以外は削除できない処理を今後追加
47 | try {
48 | const ret = await promisePool.query('delete from tasks where id = ?', [
49 | req.params.id,
50 | ])
51 | console.log(ret)
52 | } catch (err) {
53 | return res.json({ isSuccess: false, message: '削除エラー' })
54 | }
55 | return res.json({ isSuccess: true, message: 'ok' })
56 | })
57 | .put(verifyToken, async (req, res) => {
58 | // 自分のレコード以外は削除できない処理を今後追加
59 | console.log('task update start')
60 | try {
61 | console.log(req.body.task, req.params.id)
62 | const ret = await promisePool.query(
63 | 'update tasks set title = ? ,task = ? ,status = ? where id = ?',
64 | [
65 | req.body.task.title,
66 | req.body.task.task,
67 | parseInt(req.body.task.status),
68 | req.params.id,
69 | ]
70 | )
71 | console.log(ret)
72 | } catch (err) {
73 | console.log(err)
74 | return res.json({ isSuccess: false, message: '更新エラー' })
75 | }
76 | return res.json({ isSuccess: true, message: 'ok' })
77 | })
78 |
79 | module.exports = router
80 |
--------------------------------------------------------------------------------
/recipe-2-2/sample/express-app/api/users.js:
--------------------------------------------------------------------------------
1 | const router = require('express').Router()
2 | const verifyToken = require('../middlewares/verifyToken')
3 | const promisePool = require('../config/db')
4 | const bcrypt = require('bcrypt')
5 | const {
6 | validator,
7 | checkNameIsEmpty,
8 | checkEmailIsEmpty,
9 | checkEmailIsEmail,
10 | checkPasswordIsEmpty,
11 | } = require('../middlewares/validator')
12 | const registerErrorMessage = { isSuccess: false, message: '登録エラー' }
13 | router
14 | .route('/')
15 | .get(verifyToken, async (req, res) => {
16 | // usersのエンドポイントだが自身しか参照させないためwhere句で絞り込み
17 | try {
18 | const [rows, fields] = await promisePool.query(
19 | 'select id,name,email from users where id = ?',
20 | [req.decoded.id]
21 | )
22 |
23 | // verifiTokeでdecodedの設定確認用(処理では利用していない)
24 | console.log(req.decoded.id)
25 | if (req.decoded.email == 'taro@example.com') {
26 | results = {
27 | userId: req.decoded.userId,
28 | name: '太郎',
29 | }
30 | }
31 |
32 | return res.json(rows)
33 | } catch {
34 | return res.json({ isSuccess: false, message: 'error' })
35 | }
36 | })
37 | .post(
38 | [
39 | checkNameIsEmpty,
40 | checkEmailIsEmpty,
41 | checkEmailIsEmail,
42 | checkPasswordIsEmpty,
43 | ],
44 | validator,
45 | async (req, res) => {
46 | // sigUp を作成したらそちらに移行する(以下でhashを作成しusersのpasswordにinsertする)
47 | const hash = new Promise((resolve) =>
48 | bcrypt.hash(req.body.password, 10, (err, hash) => {
49 | if (err) res.json(registerErrorMessage)
50 | resolve(hash)
51 | })
52 | )
53 | const password = await hash
54 | try {
55 | const ret = await promisePool.query(
56 | 'insert into users(name,email,password) values(?,?,?)',
57 | [req.body.name, req.body.email, password]
58 | )
59 | res.json({ isSuccess: true, message: 'insertId:' + ret[0].insertId })
60 | } catch (err) {
61 | if (err.errno === -3008) {
62 | return res.json({ isSuccess: false, message: 'データベースエラー' })
63 | }
64 | return res.json(registerErrorMessage)
65 | }
66 | }
67 | )
68 |
69 | router.get('/:id', verifyToken, async (req, res) => {
70 | const [rows, fields] = await promisePool.query(
71 | 'select id,name,email,password from users where id = ?',
72 | [req.params.id]
73 | )
74 |
75 | // verifiTokeでdecodedの設定確認用(payloadにemailが含まれていることの確認)
76 | console.log(
77 | 'payload id:',
78 | req.decoded.id,
79 | 'payload email:',
80 | req.decoded.email
81 | )
82 |
83 | let results = { name: rows[0].name }
84 | return res.json(results)
85 | })
86 |
87 | module.exports = router
88 |
--------------------------------------------------------------------------------
/recipe-2-2/sample/express-app/config/db.js:
--------------------------------------------------------------------------------
1 | const mysql = require('mysql2')
2 |
3 | const pool = mysql.createPool({
4 | connectionLimit: 5,
5 | host: 'db',
6 | user: 'appuser',
7 | password: 'mysql',
8 | database: 'test',
9 | })
10 |
11 | module.exports = promisePool = pool.promise()
12 |
--------------------------------------------------------------------------------
/recipe-2-2/sample/express-app/config/index.js:
--------------------------------------------------------------------------------
1 | const ORIGIN_URL = 'http://localhost:3000'
2 |
3 | module.exports = ORIGIN_URL
4 |
--------------------------------------------------------------------------------
/recipe-2-2/sample/express-app/config/jwt.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | jwt: {
3 | secret:
4 | "f719aca24638b8127c55dcc2b92a9ba2fdd42dda5e612c7978821b99ee689a60866d28f9b91ef64129734c3ab757d5d865f1a34c1894fd6400faa831955c38d6",
5 | options: {
6 | algorithm: "HS256",
7 | expiresIn: "20m",
8 | },
9 | },
10 | }
11 |
--------------------------------------------------------------------------------
/recipe-2-2/sample/express-app/index.js:
--------------------------------------------------------------------------------
1 | const express = require('express')
2 | const app = express()
3 | const cors = require('cors')
4 | const http = require('http')
5 | const csrf = require('csurf')
6 | const cookieParser = require('cookie-parser')
7 | const server = http.createServer(app)
8 | const promisePool = require('./config/db.js')
9 | const ORIGIN_URL = require('./config/index.js')
10 |
11 | const csrfProtection = csrf({
12 | cookie: true,
13 | })
14 |
15 | app.use(cookieParser())
16 |
17 | app.use(
18 | express.urlencoded({
19 | extended: true,
20 | })
21 | )
22 |
23 | app.use(express.json())
24 |
25 | app.set('view engine', 'pug')
26 |
27 | app.use(
28 | cors({
29 | origin: ORIGIN_URL,
30 | credentials: true,
31 | optionsSuccessStatus: 200,
32 | })
33 | )
34 |
35 | app.use(csrfProtection)
36 |
37 | app.get('/', async (_, res) => {
38 | try {
39 | const [rows, fields] = await promisePool.query('select 1 as num')
40 | res.render('index', {
41 | title: 'Hey',
42 | message: `Hello there!num is ${rows[0].num}`,
43 | })
44 | } catch (err) {
45 | console.log('error:', err)
46 | res.render('index', {
47 | title: 'Hey',
48 | message: `Hello there!`,
49 | })
50 | }
51 | })
52 |
53 | app.use(
54 | '/api/v1',
55 | (() => {
56 | const router = express.Router()
57 | router.use('/login', require('./api/login.js'))
58 | router.use('/logout', require('./api/logout.js'))
59 | router.use('/users', require('./api/users.js'))
60 | router.use('/users/:id', require('./api/users.js'))
61 | router.use('/csrf-token', require('./api/csrfToken.js'))
62 | router.use('/tasks', require('./api/tasks.js'))
63 | return router
64 | })()
65 | )
66 |
67 | server.listen(5000, () => {
68 | console.log('listening on *:5000')
69 | })
70 |
--------------------------------------------------------------------------------
/recipe-2-2/sample/express-app/middlewares/validator.js:
--------------------------------------------------------------------------------
1 | const { check, validationResult } = require('express-validator')
2 |
3 | // users validator
4 | const checkNameIsEmpty = check('name').not().isEmpty().withMessage('name empty')
5 | const checkEmailIsEmpty = check('email')
6 | .not()
7 | .isEmpty()
8 | .withMessage('email empty')
9 | const checkEmailIsEmail = check('email')
10 | .isEmail()
11 | .withMessage('email not format')
12 | const checkPasswordIsEmpty = check('password')
13 | .not()
14 | .isEmpty()
15 | .withMessage('password empty')
16 |
17 | // tasks validator
18 | const checkTasksTitleIsEmpty = check('task.title')
19 | .not()
20 | .isEmpty()
21 | .withMessage('task.title empty')
22 | const checkTasksTaskIsEmpty = check('task.task')
23 | .not()
24 | .isEmpty()
25 | .withMessage('task.task empty')
26 | const checkTasksStatusIsEmpty = check('task.status')
27 | .not()
28 | .isEmpty()
29 | .withMessage('task.status empty')
30 |
31 | function validator(req, res, next) {
32 | const errors = validationResult(req)
33 | if (!errors.isEmpty()) {
34 | const messages = errors.array()
35 | return res.status(422).json({ isSuccess: false, message: messages[0].msg })
36 | }
37 | next()
38 | }
39 |
40 | exports.checkNameIsEmpty = checkNameIsEmpty
41 | exports.checkEmailIsEmpty = checkEmailIsEmpty
42 | exports.checkEmailIsEmail = checkEmailIsEmail
43 | exports.checkPasswordIsEmpty = checkPasswordIsEmpty
44 |
45 | exports.checkTasksTitleIsEmpty = checkTasksTitleIsEmpty
46 | exports.checkTasksTaskIsEmpty = checkTasksTaskIsEmpty
47 | exports.checkTasksStatusIsEmpty = checkTasksStatusIsEmpty
48 |
49 | exports.validator = validator
50 |
--------------------------------------------------------------------------------
/recipe-2-2/sample/express-app/middlewares/verifyToken.js:
--------------------------------------------------------------------------------
1 | const jwt = require('jsonwebtoken')
2 | const config = require('../config/jwt.config')
3 |
4 | function verifyToken(req, res, next) {
5 | const authHeader = req.headers.authorization
6 | const token = req.cookies.token
7 | ? req.cookies.token
8 | : authHeader && authHeader.split(' ')[1]
9 | if (token) {
10 | jwt.verify(token, config.jwt.secret, function (error, decoded) {
11 | if (error) {
12 | return res.status(403).send({
13 | isSuccess: false,
14 | message: 'トークンの認証に失敗しました。',
15 | })
16 | } else {
17 | req.decoded = decoded
18 | next()
19 | }
20 | })
21 | } else {
22 | return res.status(401).send({
23 | isSuccess: false,
24 | message: 'トークンがありません。',
25 | })
26 | }
27 | }
28 | module.exports = verifyToken
29 |
--------------------------------------------------------------------------------
/recipe-2-2/sample/express-app/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "express-app",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "keywords": [],
10 | "author": "",
11 | "license": "ISC",
12 | "dependencies": {
13 | "cookie-parser": "^1.4.5",
14 | "cors": "^2.8.5",
15 | "csurf": "^1.11.0",
16 | "express": "^4.17.1",
17 | "jsonwebtoken": "^8.5.1",
18 | "mysql": "^2.18.1"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/recipe-2-2/sample/express-app/views/index.pug:
--------------------------------------------------------------------------------
1 | html
2 | head
3 | title= title
4 | body
5 | h1= message
6 |
--------------------------------------------------------------------------------
/recipe-2-2/sample/react-app/src/Index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 | import App from './components/App'
4 | import './index.css'
5 | import { Provider } from 'react-redux'
6 | import { store } from './app/store'
7 |
8 | ReactDOM.render(
9 |
10 |
11 |
12 |
13 | ,
14 | document.getElementById('root')
15 | )
16 |
--------------------------------------------------------------------------------
/recipe-2-2/sample/react-app/src/app/store.js:
--------------------------------------------------------------------------------
1 | import { configureStore } from '@reduxjs/toolkit'
2 | import authReducer from '../features/auth/authSlice'
3 | import tasksReducer from '../features/tasks/tasksSlice'
4 |
5 | export const store = configureStore({
6 | reducer: {
7 | auth: authReducer,
8 | tasks: tasksReducer,
9 | },
10 | })
11 |
--------------------------------------------------------------------------------
/recipe-2-2/sample/react-app/src/components/App.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react'
2 |
3 | import Layout from './Layout'
4 | import { useDispatch, useSelector } from 'react-redux'
5 | import {
6 | fetchCsrfTokenAsync,
7 | fetchTokenAsync,
8 | selectCsrfTokenState,
9 | } from '../features/auth/authSlice'
10 |
11 | const App = () => {
12 | const dispatch = useDispatch()
13 | const csrfToken = useSelector(selectCsrfTokenState)
14 | useEffect(() => {
15 | dispatch(fetchCsrfTokenAsync())
16 | dispatch(fetchTokenAsync())
17 | }, [dispatch])
18 |
19 | console.log('app csrfToken', csrfToken)
20 |
21 | return (
22 | <>
23 |
24 | >
25 | )
26 | }
27 |
28 | export default App
29 |
--------------------------------------------------------------------------------
/recipe-2-2/sample/react-app/src/components/Footer.js:
--------------------------------------------------------------------------------
1 | import { memo } from 'react'
2 | import * as C from '../config/index'
3 |
4 | const NoMemoFooter = () => {
5 | return (
6 | <>
7 |
11 | >
12 | )
13 | }
14 |
15 | export const Footer = memo(NoMemoFooter)
16 |
--------------------------------------------------------------------------------
/recipe-2-2/sample/react-app/src/components/Header.js:
--------------------------------------------------------------------------------
1 | import { memo, useState } from 'react'
2 | import { LogoutIcon } from '@heroicons/react/outline'
3 | import { selectIsAuthentication } from '../features/auth/authSlice'
4 | import { useSelector } from 'react-redux'
5 | import * as C from '../config/index'
6 | import Modal from './Modal'
7 |
8 | const NoMemoHeader = () => {
9 | const [modalOn, setModalOn] = useState(false)
10 | const isLogin = useSelector(selectIsAuthentication)
11 |
12 | return (
13 | <>
14 |
15 |
37 |
38 | >
39 | )
40 | }
41 |
42 | export const Header = memo(NoMemoHeader)
43 |
--------------------------------------------------------------------------------
/recipe-2-2/sample/react-app/src/components/Layout.js:
--------------------------------------------------------------------------------
1 | import { useState } from 'react'
2 | import { useSelector } from 'react-redux'
3 | import Main from './Main'
4 | import { Header } from './Header'
5 | import { Footer } from './Footer'
6 | import { Login } from './Login'
7 | import { SignUp } from './SignUp'
8 | import {
9 | selectIsAuthentication,
10 | selectIsSignUp,
11 | } from '../features/auth/authSlice'
12 |
13 | const Layout = () => {
14 | const isLogin = useSelector(selectIsAuthentication)
15 | const isSignUp = useSelector(selectIsSignUp)
16 | const [user, setUser] = useState({
17 | name: 'taro',
18 | email: 'taro@example.com',
19 | password: 'abcd',
20 | })
21 | return (
22 |
23 |
24 | {isLogin ? null : isSignUp ? (
25 |
26 | ) : (
27 |
28 | )}
29 | {isLogin ? : null}
30 |
31 |
32 | )
33 | }
34 |
35 | export default Layout
36 |
--------------------------------------------------------------------------------
/recipe-2-2/sample/react-app/src/components/Login.js:
--------------------------------------------------------------------------------
1 | import { memo } from 'react'
2 | import { useDispatch, useSelector } from 'react-redux'
3 | import {
4 | selectCsrfTokenState,
5 | postAuthenticationAsync,
6 | toggleSignUp,
7 | } from '../features/auth/authSlice'
8 |
9 | const NoMemoLogin = ({ user, setUser, setIsSignUp }) => {
10 | const dispatch = useDispatch()
11 | const csrfToken = useSelector(selectCsrfTokenState)
12 |
13 | return (
14 | <>
15 |
16 |
22 |
setUser({ ...user, email: e.target.value })}
30 | />
31 |
32 |
38 |
setUser({ ...user, password: e.target.value })}
45 | />
46 |
Please choose a password.
47 |
48 |
49 |
65 |
66 | {
68 | e.preventDefault()
69 | dispatch(toggleSignUp())
70 | }}
71 | >
72 | Switch SignUp?(CLICK!!)
73 |
74 |
75 |
76 |
77 | >
78 | )
79 | }
80 |
81 | export const Login = memo(NoMemoLogin)
82 |
--------------------------------------------------------------------------------
/recipe-2-2/sample/react-app/src/components/Main.js:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react'
2 | import { useDispatch, useSelector } from 'react-redux'
3 | import {
4 | selectTokenState,
5 | selectCsrfTokenState,
6 | } from '../features/auth/authSlice'
7 | import {
8 | fetchTasksAsync,
9 | selectTasksState,
10 | selectDeleteTaskState,
11 | selectUpdateTaskState,
12 | selectInsertTaskState,
13 | } from '../features/tasks/tasksSlice'
14 | import Task from './Task'
15 | import TaskInsertModal from './TaskInsertModal'
16 |
17 | const Main = () => {
18 | const dispatch = useDispatch()
19 | const token = useSelector(selectTokenState)
20 | const csrfToken = useSelector(selectCsrfTokenState)
21 | const tasks = useSelector(selectTasksState)
22 | const deleteTaskState = useSelector(selectDeleteTaskState)
23 | const updateTaskState = useSelector(selectUpdateTaskState)
24 | const insertTaskState = useSelector(selectInsertTaskState)
25 | const [insertModalOn, setInsertModalOn] = useState(false)
26 |
27 | useEffect(() => {
28 | dispatch(fetchTasksAsync({ token: token }))
29 | }, [dispatch, token, deleteTaskState, updateTaskState, insertTaskState])
30 |
31 | return (
32 |
33 | {tasks.length ? (
34 |
35 |
36 |
37 | タスク名 |
38 | 内容 |
39 | ステータス |
40 | 修正 |
41 | 削除 |
42 |
43 |
44 |
45 | {tasks.map((task) => (
46 |
47 | ))}
48 |
49 |
50 | ) : null}
51 |
52 |
58 |
59 | {insertModalOn ? (
60 |
64 | ) : null}
65 |
66 | )
67 | }
68 |
69 | export default Main
70 |
--------------------------------------------------------------------------------
/recipe-2-2/sample/react-app/src/components/Modal.js:
--------------------------------------------------------------------------------
1 | import { Fragment, useRef, useState } from 'react'
2 | import { useSelector, useDispatch } from 'react-redux'
3 | import { Dialog, Transition } from '@headlessui/react'
4 | import { LogoutIcon } from '@heroicons/react/outline'
5 | import { selectCsrfTokenState, logout } from '../features/auth/authSlice'
6 |
7 | const Modal = ({ setModalOn }) => {
8 | const [open, setOpen] = useState(true)
9 | const cancelButtonRef = useRef(null)
10 | const csrfToken = useSelector(selectCsrfTokenState)
11 | const dispatch = useDispatch()
12 |
13 | return (
14 |
15 |
106 |
107 | )
108 | }
109 |
110 | export default Modal
111 |
--------------------------------------------------------------------------------
/recipe-2-2/sample/react-app/src/components/SignUp.js:
--------------------------------------------------------------------------------
1 | import { memo } from 'react'
2 | import { useDispatch, useSelector } from 'react-redux'
3 | import { signUp, toggleSignUp } from '../features/auth/authSlice'
4 | import { selectCsrfTokenState } from '../features/auth/authSlice'
5 | const NoMemoSignUp = ({ user, setUser }) => {
6 | const dispatch = useDispatch()
7 | const csrfToken = useSelector(selectCsrfTokenState)
8 |
9 | return (
10 | <>
11 |
12 |
18 |
setUser({ ...user, name: e.target.value })}
26 | />
27 |
33 |
setUser({ ...user, email: e.target.value })}
40 | />
41 |
42 |
48 |
setUser({ ...user, password: e.target.value })}
55 | />
56 |
Please choose a password.
57 |
58 |
59 |
76 |
77 | {
79 | e.preventDefault()
80 | dispatch(toggleSignUp())
81 | }}
82 | >
83 | Switch Login?(CLICK!!)
84 |
85 |
86 |
87 |
88 | >
89 | )
90 | }
91 |
92 | export const SignUp = memo(NoMemoSignUp)
93 |
--------------------------------------------------------------------------------
/recipe-2-2/sample/react-app/src/components/Task.js:
--------------------------------------------------------------------------------
1 | import { useState } from 'react'
2 | import { useSelector } from 'react-redux'
3 | import { selectCsrfTokenState } from '../features/auth/authSlice'
4 | import TaskDeleteModal from './TaskDeleteModal'
5 | import TaskUpdateModal from './TaskUpdateModal'
6 |
7 | const Task = ({ task }) => {
8 | const csrfToken = useSelector(selectCsrfTokenState)
9 | const [updateModalOn, setUpdateModalOn] = useState(false)
10 | const [deleteModalOn, setDeleteModalOn] = useState(false)
11 |
12 | return (
13 | <>
14 |
15 |
16 | {task.title}
17 | |
18 |
19 | {task.task}
20 | |
21 |
22 | {task.status_name}
23 | |
24 |
25 |
33 | |
34 |
35 |
43 | |
44 |
45 | {updateModalOn ? (
46 |
50 | ) : null}
51 | {deleteModalOn ? (
52 |
56 | ) : null}
57 |
58 | >
59 | )
60 | }
61 |
62 | export default Task
63 |
--------------------------------------------------------------------------------
/recipe-2-2/sample/react-app/src/components/TaskDeleteModal.js:
--------------------------------------------------------------------------------
1 | import { Fragment, useRef, useState } from 'react'
2 | import { useDispatch } from 'react-redux'
3 | import { Dialog, Transition } from '@headlessui/react'
4 | import { TrashIcon } from '@heroicons/react/outline'
5 | import { deleteTaskAsync } from '../features/tasks/tasksSlice'
6 |
7 | const TaskDeleteModal = ({ setDeleteModalOn, credentials }) => {
8 | const [open, setOpen] = useState(true)
9 | const cancelButtonRef = useRef(null)
10 | const dispatch = useDispatch()
11 |
12 | return (
13 |
14 |
110 |
111 | )
112 | }
113 |
114 | export default TaskDeleteModal
115 |
--------------------------------------------------------------------------------
/recipe-2-2/sample/react-app/src/components/TaskInsertModal.js:
--------------------------------------------------------------------------------
1 | import { Fragment, useRef, useState } from 'react'
2 | import { useDispatch } from 'react-redux'
3 | import { Dialog, Transition } from '@headlessui/react'
4 | import { RefreshIcon } from '@heroicons/react/outline'
5 | import { insertTaskAsync } from '../features/tasks/tasksSlice'
6 |
7 | const TaskInsertModal = ({ setInsertModalOn, credentials }) => {
8 | const [open, setOpen] = useState(true)
9 | const [taskState, setTaskState] = useState({
10 | title: '',
11 | task: '',
12 | status: 1,
13 | })
14 | const cancelButtonRef = useRef(null)
15 | const dispatch = useDispatch()
16 |
17 | return (
18 |
19 |
162 |
163 | )
164 | }
165 |
166 | export default TaskInsertModal
167 |
--------------------------------------------------------------------------------
/recipe-2-2/sample/react-app/src/components/TaskUpdateModal.js:
--------------------------------------------------------------------------------
1 | import { Fragment, useRef, useState } from 'react'
2 | import { useDispatch } from 'react-redux'
3 | import { Dialog, Transition } from '@headlessui/react'
4 | import { RefreshIcon } from '@heroicons/react/outline'
5 | import { updateTaskAsync } from '../features/tasks/tasksSlice'
6 |
7 | const TaskUpdateModal = ({ setUpdateModalOn, credentials }) => {
8 | const [open, setOpen] = useState(true)
9 | const [taskState, setTaskState] = useState({
10 | id: credentials.task.id,
11 | title: credentials.task.title,
12 | task: credentials.task.task,
13 | status: credentials.task.status,
14 | status_name: credentials.task.status_name,
15 | })
16 | const cancelButtonRef = useRef(null)
17 | const dispatch = useDispatch()
18 |
19 | return (
20 |
21 |
164 |
165 | )
166 | }
167 |
168 | export default TaskUpdateModal
169 |
--------------------------------------------------------------------------------
/recipe-2-2/sample/react-app/src/config/index.js:
--------------------------------------------------------------------------------
1 | export const URL = 'http://localhost:5000'
2 | export const SITE_NAME = 'Super Web Site'
3 |
--------------------------------------------------------------------------------
/recipe-2-2/sample/react-app/src/features/auth/authSlice.js:
--------------------------------------------------------------------------------
1 | import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'
2 | import * as C from '../../config/index'
3 |
4 | const initialState = {
5 | isAuthentication: false,
6 | csrfToken: '',
7 | csrfFetchState: 'idle',
8 | token: '',
9 | tokenFetchState: 'idle',
10 | authenticationState: 'idle',
11 | postAuthenticationState: 'idle',
12 | logoutState: 'idle',
13 | isSignUp: false,
14 | signUpState: 'idle',
15 | }
16 |
17 | export const fetchCsrfTokenAsync = createAsyncThunk(
18 | 'auth/fetchCsrfToken',
19 | async () => {
20 | const res = await fetch(C.URL + '/api/v1/csrf-token', {
21 | method: 'GET',
22 | credentials: 'include',
23 | })
24 | const data = await res.json()
25 | return data.csrfToken
26 | }
27 | )
28 |
29 | export const fetchTokenAsync = createAsyncThunk('auth/fetchToken', async () => {
30 | const res = await fetch(C.URL + '/api/v1/login', {
31 | method: 'GET',
32 | credentials: 'include',
33 | })
34 | const data = await res.json()
35 | return data
36 | })
37 |
38 | export const authenticationAsync = createAsyncThunk(
39 | 'auth/authentication',
40 | async (credentials) => {
41 | const res = await fetch(C.URL + '/api/v1/login', {
42 | method: 'POST',
43 | mode: 'cors',
44 | cache: 'no-cache',
45 | credentials: 'include',
46 | headers: {
47 | 'Content-Type': 'application/json',
48 | 'CSRF-Token': credentials.csrfToken,
49 | },
50 | redirect: 'follow',
51 | body: JSON.stringify({
52 | email: credentials.email,
53 | password: credentials.password,
54 | }),
55 | })
56 | const data = await res.json()
57 | return data
58 | }
59 | )
60 |
61 | export const signUp = createAsyncThunk('auth/signUp', async (credentials) => {
62 | const res = await fetch(C.URL + '/api/v1/users', {
63 | method: 'POST',
64 | mode: 'cors',
65 | cache: 'no-cache',
66 | credentials: 'include',
67 | headers: {
68 | 'Content-Type': 'application/json',
69 | 'CSRF-Token': credentials.csrfToken,
70 | },
71 | redirect: 'follow',
72 | body: JSON.stringify({
73 | name: credentials.name,
74 | email: credentials.email,
75 | password: credentials.password,
76 | }),
77 | })
78 | const data = await res.json()
79 | return data
80 | })
81 |
82 | export const postAuthenticationAsync = createAsyncThunk(
83 | 'auth/postAuthentication',
84 | async (credentials) => {
85 | const ret = await fetch(C.URL + '/api/v1/login', {
86 | method: 'POST',
87 | mode: 'cors',
88 | cache: 'no-cache',
89 | credentials: 'include',
90 | headers: {
91 | 'Content-Type': 'application/json',
92 | 'CSRF-Token': credentials.csrfToken,
93 | },
94 | redirect: 'follow',
95 | body: JSON.stringify({
96 | email: credentials.email,
97 | password: credentials.password,
98 | }),
99 | })
100 | const data = await ret.json()
101 | return data
102 | }
103 | )
104 |
105 | export const logout = createAsyncThunk('auth/logout', async (credentials) => {
106 | const ret = await fetch(C.URL + '/api/v1/logout', {
107 | method: 'POST',
108 | mode: 'cors',
109 | cache: 'no-cache',
110 | credentials: 'include',
111 | headers: {
112 | 'Content-Type': 'application/json',
113 | 'CSRF-Token': credentials.csrfToken,
114 | },
115 | redirect: 'follow',
116 | })
117 | const data = await ret.json()
118 | return data
119 | })
120 |
121 | export const authSlice = createSlice({
122 | name: 'auth',
123 | initialState,
124 | reducers: {
125 | successAuthentication: (state) => {
126 | state.isAuthentication = true
127 | },
128 | setToken: (state, action) => {
129 | state.token = action.payload
130 | },
131 | clearToken: (state) => {
132 | state.token = ''
133 | state.isAuthentication = false
134 | },
135 | toggleSignUp: (state) => {
136 | state.isSignUp = !state.isSignUp
137 | },
138 | },
139 | extraReducers: (builder) => {
140 | builder
141 | .addCase(fetchCsrfTokenAsync.pending, (state) => {
142 | state.csrfFetchState = 'loading'
143 | })
144 | .addCase(fetchCsrfTokenAsync.fulfilled, (state, action) => {
145 | state.csrfToken = action.payload
146 | state.csrfFetchState = 'idle'
147 | })
148 | .addCase(fetchTokenAsync.pending, (state) => {
149 | state.tokenFetchState = 'loading'
150 | })
151 | .addCase(fetchTokenAsync.fulfilled, (state, action) => {
152 | if (action.payload.isSuccess) {
153 | state.token = action.payload.token
154 | state.isAuthentication = true
155 | }
156 | state.tokenFetchState = 'idle'
157 | })
158 | .addCase(authenticationAsync.pending, (state) => {
159 | state.authenticationState = 'loading'
160 | })
161 | .addCase(authenticationAsync.fulfilled, (state, action) => {
162 | if (action.payload.isSuccess) {
163 | state.token = action.payload.token
164 | state.isAuthentication = true
165 | }
166 | state.authenticationState = 'idle'
167 | })
168 | .addCase(postAuthenticationAsync.pending, (state) => {
169 | state.postAuthenticationState = 'loading'
170 | })
171 | .addCase(postAuthenticationAsync.fulfilled, (state, action) => {
172 | if (action.payload.isSuccess) {
173 | state.token = action.payload.token
174 | state.isAuthentication = true
175 | } else {
176 | console.log(action.payload)
177 | alert(action.payload.message)
178 | }
179 | state.postAuthenticationState = 'idle'
180 | })
181 | .addCase(postAuthenticationAsync.rejected, (state) => {
182 | state.postAuthenticationState = 'rejected'
183 | })
184 | .addCase(logout.pending, (state) => {
185 | state.logoutState = 'loading'
186 | })
187 | .addCase(logout.fulfilled, (state, action) => {
188 | state.token = ''
189 | state.isAuthentication = false
190 | state.logoutState = 'idle'
191 | })
192 | .addCase(signUp.pending, (state) => {
193 | state.signUpState = 'loading'
194 | })
195 | .addCase(signUp.fulfilled, (state, action) => {
196 | if (action.payload.isSuccess) {
197 | state.isSignUp = false
198 | } else {
199 | console.log(action.payload)
200 | alert(action.payload.message)
201 | }
202 | state.signUpState = 'idle'
203 | })
204 | },
205 | })
206 |
207 | export const { setToken, clearToken, successAuthentication, toggleSignUp } =
208 | authSlice.actions
209 | export const selectIsAuthentication = (state) => state.auth.isAuthentication
210 | export const selectCsrfTokenState = (state) => state.auth.csrfToken
211 | export const selectTokenState = (state) => state.auth.token
212 | export const selectIsSignUp = (state) => state.auth.isSignUp
213 | export default authSlice.reducer
214 |
--------------------------------------------------------------------------------
/recipe-2-2/sample/react-app/src/features/tasks/tasksSlice.js:
--------------------------------------------------------------------------------
1 | import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'
2 | import * as C from '../../config/index'
3 |
4 | const initialState = {
5 | tasksStatus: 'idle',
6 | deleteTaskStatus: 'idle',
7 | updateTaskStatus: 'idle',
8 | insertTaskStatus: 'idle',
9 | value: [],
10 | }
11 |
12 | export const fetchTasksAsync = createAsyncThunk(
13 | 'tasks/fetch',
14 | async (credentials) => {
15 | const res = await fetch(C.URL + '/api/v1/tasks', {
16 | method: 'GET',
17 | credentials: 'include',
18 | headers: {
19 | Authorization: `Bearer ${credentials.token}`,
20 | },
21 | })
22 | const data = await res.json()
23 | return data
24 | }
25 | )
26 |
27 | export const deleteTaskAsync = createAsyncThunk(
28 | 'tasks/delete',
29 | async (credentials) => {
30 | const res = await fetch(C.URL + '/api/v1/tasks/' + credentials.id, {
31 | method: 'DELETE',
32 | mode: 'cors',
33 | cache: 'no-cache',
34 | credentials: 'include',
35 | headers: {
36 | 'Content-Type': 'application/json',
37 | 'CSRF-Token': credentials.csrfToken,
38 | },
39 | redirect: 'follow',
40 | body: JSON.stringify({ id: credentials.id }),
41 | })
42 | const data = res.json()
43 | return data
44 | }
45 | )
46 |
47 | export const updateTaskAsync = createAsyncThunk(
48 | 'tasks/update',
49 | async (credentials) => {
50 | console.log(credentials.task)
51 | const res = await fetch(C.URL + '/api/v1/tasks/' + credentials.task.id, {
52 | method: 'PUT',
53 | mode: 'cors',
54 | cache: 'no-cache',
55 | credentials: 'include',
56 | headers: {
57 | 'Content-Type': 'application/json',
58 | 'CSRF-Token': credentials.csrfToken,
59 | },
60 | redirect: 'follow',
61 | body: JSON.stringify({ task: credentials.task }),
62 | })
63 | const data = res.json()
64 | return data
65 | }
66 | )
67 |
68 | export const insertTaskAsync = createAsyncThunk(
69 | 'tasks/insert',
70 | async (credentials) => {
71 | const ret = await fetch(C.URL + '/api/v1/tasks', {
72 | method: 'POST',
73 | mode: 'cors',
74 | cache: 'no-cache',
75 | credentials: 'include',
76 | headers: {
77 | 'Content-Type': 'application/json',
78 | 'CSRF-Token': credentials.csrfToken,
79 | },
80 | redirect: 'follow',
81 | body: JSON.stringify({ task: credentials.task }),
82 | })
83 | const data = await ret.json()
84 | return data
85 | }
86 | )
87 |
88 | export const tasksSlice = createSlice({
89 | name: 'tasks',
90 | initialState,
91 | reducers: {},
92 | extraReducers: (builder) => {
93 | builder
94 | .addCase(fetchTasksAsync.pending, (state) => {
95 | state.tasksStatus = 'loading'
96 | })
97 | .addCase(fetchTasksAsync.fulfilled, (state, action) => {
98 | state.tasksStatus = 'idle'
99 | state.value = action.payload
100 | })
101 | .addCase(deleteTaskAsync.pending, (state) => {
102 | state.deleteTaskStatus = 'loading'
103 | })
104 | .addCase(deleteTaskAsync.fulfilled, (state, action) => {
105 | if (!action.payload.isSuccess) {
106 | alert('削除エラー')
107 | }
108 | state.deleteTaskStatus = 'idle'
109 | })
110 | .addCase(updateTaskAsync.pending, (state) => {
111 | state.updateTaskStatus = 'loading'
112 | })
113 | .addCase(updateTaskAsync.fulfilled, (state, action) => {
114 | if (!action.payload.isSuccess) {
115 | alert('更新エラー')
116 | }
117 | state.updateTaskStatus = 'idle'
118 | })
119 | .addCase(insertTaskAsync.pending, (state) => {
120 | state.insertTaskStatus = 'loading'
121 | })
122 | .addCase(insertTaskAsync.fulfilled, (state, action) => {
123 | if (!action.payload.isSuccess) {
124 | alert(action.payload.message)
125 | }
126 | state.insertTaskStatus = 'idle'
127 | })
128 | },
129 | })
130 |
131 | export const selectTasksState = (state) => state.tasks.value
132 | export const selectDeleteTaskState = (state) => state.tasks.deleteTaskStatus
133 | export const selectUpdateTaskState = (state) => state.tasks.updateTaskStatus
134 | export const selectInsertTaskState = (state) => state.tasks.insertTaskStatus
135 | export default tasksSlice.reducer
136 |
--------------------------------------------------------------------------------
/recipe-2-2/sample/react-app/src/index.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
--------------------------------------------------------------------------------
/recipe-2.5/README.md:
--------------------------------------------------------------------------------
1 | # recipe-2.5 Docker-React-Express-MySQL-HTTPS-JWT
2 |
3 | recipe-2.1 に JWT,HTTPS を追加
4 |
5 | ```
6 | $ docker-compose up --build -d
7 | ```
8 |
9 | React アプリは`https://localhost:10443`で開く(ブラウザから怒られるがそのまま開く)
10 |
11 | `jwt.config.js`の`secret`は以下で作成
12 |
13 | ```
14 | $ node -e "console.log(require('jsonwebtoken').sign({username:'hoge'},'my_secret'))"
15 | ```
16 |
--------------------------------------------------------------------------------
/recipe-2.5/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "3"
2 | services:
3 | https-portal:
4 | image: steveltn/https-portal:latest
5 | ports:
6 | - 443:443
7 | restart: always
8 | environment:
9 | DOMAINS: "localhost -> http://express-app:5000"
10 | STAGE: local
11 | https-portal2:
12 | image: steveltn/https-portal:latest
13 | ports:
14 | - 10443:443
15 | restart: always
16 | environment:
17 | DOMAINS: "localhost -> http://react-app:3000"
18 | STAGE: local
19 | react-app:
20 | build: docker/react-app
21 | tty: true
22 | volumes:
23 | - react-app:/react-app
24 | ports:
25 | - "3000:3000"
26 | user: root
27 | express-app:
28 | build: docker/express-app
29 | tty: true
30 | volumes:
31 | - express-app:/express-app
32 | ports:
33 | - "5000:5000"
34 | user: root
35 | depends_on:
36 | - db
37 | db:
38 | build: docker/mysql
39 | tty: true
40 | volumes:
41 | - mysql:/var/lib/mysql
42 | - mysql-log/var/log/mysql
43 | environment:
44 | MYSQL_DATABASE: test
45 | MYSQL_ROOT_PASSWORD: mysql
46 | MYSQL_USER: appuser
47 | MYSQL_PASSWORD: mysql
48 | expose:
49 | - "3306"
50 | ports:
51 | - "3307:3306"
52 | volumes:
53 | react-app:
54 | express-app:
55 | mysql:
56 | mysql-log:
57 |
--------------------------------------------------------------------------------
/recipe-2.5/docker/express-app/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:latest
2 |
3 | ENV APP_PATH=/express-app
4 | RUN mkdir $APP_PATH
5 | WORKDIR $APP_PATH
6 |
7 | RUN npm init -y
8 | RUN npm install -y express cors mysql jsonwebtoken
--------------------------------------------------------------------------------
/recipe-2.5/docker/mysql/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM mysql:latest
2 | ADD ./conf.d/my.cnf /etc/mysql/conf.d/my.cnf
--------------------------------------------------------------------------------
/recipe-2.5/docker/mysql/conf.d/my.cnf:
--------------------------------------------------------------------------------
1 | [mysqld]
2 | innodb-buffer-pool-size=128M
3 | default-authentication-plugin=mysql_native_password
--------------------------------------------------------------------------------
/recipe-2.5/docker/react-app/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:latest
2 |
3 | ENV APP_PATH=/react-app
4 | RUN mkdir $APP_PATH
5 | WORKDIR $APP_PATH
6 |
7 | RUN npx create-react-app .
--------------------------------------------------------------------------------
/recipe-2.5/sample/express-app/api/login.js:
--------------------------------------------------------------------------------
1 | const router = require("express").Router()
2 | const jwt = require("jsonwebtoken")
3 | const config = require("../config/jwt.config")
4 |
5 | router.get("/", (req, res) => {
6 | console.log("login get")
7 | res.send("hello")
8 | })
9 |
10 | router.post("/", (req, res) => {
11 | if (
12 | (req.body.userId == "001" && req.body.passWord == "qwerty") ||
13 | (req.body.userId == "002" && req.body.passWord == "asdfgh")
14 | ) {
15 | const payload = {
16 | userId: req.body.userId,
17 | }
18 |
19 | const token = jwt.sign(payload, config.jwt.secret, config.jwt.options)
20 |
21 | console.log(token)
22 |
23 | res.cookie("token", token, {
24 | httpOnly: true,
25 | domain: "localhost",
26 | path: "/",
27 | sameSite: "none",
28 | secure: true,
29 | })
30 |
31 | res.json({
32 | isSuccess: true,
33 | token: token,
34 | })
35 | } else {
36 | res.json({
37 | isSuccess: false,
38 | message: "ユーザーIDまたはパスワードが違います。",
39 | })
40 | }
41 | })
42 | module.exports = router
43 |
--------------------------------------------------------------------------------
/recipe-2.5/sample/express-app/api/users.js:
--------------------------------------------------------------------------------
1 | const router = require("express").Router()
2 | const verifyToken = require("../middlewares/verifyToken")
3 |
4 | router.get("/", verifyToken, (req, res) => {
5 | let results = {}
6 | if (req.decoded.userId == "001") {
7 | results = {
8 | userId: req.decoded.userId,
9 | name: "太郎",
10 | }
11 | } else if (req.decoded.userId == "002") {
12 | results = {
13 | userId: req.decoded.userId,
14 | name: "二郎",
15 | }
16 | }
17 |
18 | res.json(results)
19 | })
20 | module.exports = router
21 |
--------------------------------------------------------------------------------
/recipe-2.5/sample/express-app/config/jwt.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | jwt: {
3 | secret:
4 | "f719aca24638b8127c55dcc2b92a9ba2fdd42dda5e612c7978821b99ee689a60866d28f9b91ef64129734c3ab757d5d865f1a34c1894fd6400faa831955c38d6",
5 | options: {
6 | algorithm: "HS256",
7 | expiresIn: "20m",
8 | },
9 | },
10 | }
11 |
--------------------------------------------------------------------------------
/recipe-2.5/sample/express-app/index.js:
--------------------------------------------------------------------------------
1 | const express = require("express")
2 | const app = express()
3 | const cors = require("cors")
4 | const http = require("http")
5 | const server = http.createServer(app)
6 | const mysql = require("mysql")
7 |
8 | app.use(
9 | express.urlencoded({
10 | extended: true,
11 | })
12 | )
13 | app.use(express.json())
14 |
15 | app.use(
16 | cors({
17 | origin: "https://localhost:10443",
18 | credentials: true,
19 | optionsSuccessStatus: 200,
20 | })
21 | )
22 |
23 | app.get("/", (req, res) => {
24 | let message = "Hello Express App!!solution is -> "
25 | const connection = mysql.createConnection({
26 | host: "db",
27 | user: "appuser",
28 | password: "mysql",
29 | database: "test",
30 | })
31 | connection.connect()
32 | connection.query("SELECT 1 + 1 AS solution", (err, rows, fields) => {
33 | if (err) throw err
34 | message += rows[0].solution
35 | connection.end()
36 | res.json({
37 | message: message,
38 | })
39 | })
40 | })
41 |
42 | app.post("/", (req, res) => {
43 | console.log(req.body)
44 | res.json({
45 | message: "Hello Express Post " + req.body.name + " App!!",
46 | })
47 | })
48 |
49 | app.use(
50 | "/api/v1",
51 | (() => {
52 | const router = express.Router()
53 | router.use("/login", require("./api/login.js"))
54 | router.use("/users", require("./api/users.js"))
55 | return router
56 | })()
57 | )
58 |
59 | server.listen(5000, () => {
60 | console.log("listening on *:5000")
61 | })
62 |
--------------------------------------------------------------------------------
/recipe-2.5/sample/express-app/middlewares/verifyToken.js:
--------------------------------------------------------------------------------
1 | const jwt = require("jsonwebtoken")
2 | const config = require("../config/jwt.config")
3 |
4 | function verifyToken(req, res, next) {
5 | const authHeader = req.headers.authorization
6 | const token = authHeader && authHeader.split(" ")[1]
7 |
8 | if (token) {
9 | jwt.verify(token, config.jwt.secret, function (error, decoded) {
10 | if (error) {
11 | return res.status(403).send({
12 | isSuccess: false,
13 | message: "トークンの認証に失敗しました。",
14 | })
15 | } else {
16 | req.decoded = decoded
17 | next()
18 | }
19 | })
20 | } else {
21 | return res.status(401).send({
22 | isSuccess: false,
23 | message: "トークンがありません。",
24 | })
25 | }
26 | }
27 | module.exports = verifyToken
28 |
--------------------------------------------------------------------------------
/recipe-2.5/sample/react-app/Index.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import ReactDOM from "react-dom"
3 | import App from "./components/App"
4 |
5 | ReactDOM.render(
6 |
7 |
8 | ,
9 | document.getElementById("root")
10 | )
11 |
--------------------------------------------------------------------------------
/recipe-2.5/sample/react-app/components/App.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react"
2 |
3 | const App = () => {
4 | const [message, setMessage] = useState("hoge")
5 | const [token, setToken] = useState("")
6 |
7 | useEffect(() => {
8 | fetch("https://localhost")
9 | .then((res) => res.json())
10 | .then((res) => setMessage(res.message))
11 | }, [])
12 | return (
13 |
14 | hello!:{message}
15 |
60 |
61 | )
62 | }
63 |
64 | export default App
65 |
--------------------------------------------------------------------------------
/recipe-docker-nginx-express/README.md:
--------------------------------------------------------------------------------
1 | # recipe-docker-nginx-express
2 |
3 | docker-compose での Web(Nginx) + API(express) の環境構築
4 |
5 | ## Build
6 |
7 | ```
8 | docker-compose up -d
9 | ```
10 |
11 | ## Access
12 |
13 | `localhost:8080`
14 |
--------------------------------------------------------------------------------
/recipe-docker-nginx-express/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "3"
2 | services:
3 | web:
4 | build:
5 | context: ./docker/nginx
6 | dockerfile: Dockerfile
7 | image: web
8 | container_name: web
9 | ports:
10 | - 8080:80
11 |
12 | app:
13 | build:
14 | context: ./docker/api
15 | dockerfile: Dockerfile
16 | tty: true
17 | image: api
18 | container_name: api
19 | ports:
20 | - 5001:5001
21 | user: root
22 | volumes:
23 | - api:/api
24 | command: ["npm", "start"]
25 | volumes:
26 | api:
27 |
--------------------------------------------------------------------------------
/recipe-docker-nginx-express/docker/api/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:latest
2 |
3 | ENV APP_PATH=/api
4 | RUN mkdir $APP_PATH
5 | WORKDIR $APP_PATH
6 |
7 | COPY package.json /api/
8 | COPY index.js /api/
9 |
10 | RUN npm install
--------------------------------------------------------------------------------
/recipe-docker-nginx-express/docker/api/index.js:
--------------------------------------------------------------------------------
1 | const express = require('express')
2 | const app = express()
3 | const cors = require('cors')
4 | const http = require('http')
5 | const server = http.createServer(app)
6 |
7 | app.use(
8 | cors({
9 | origin: 'http://localhost:3000',
10 | credentials: true,
11 | optionsSuccessStatus: 200,
12 | })
13 | )
14 |
15 | app.get('/', (req, res) => {
16 | res.json({
17 | message: 'Hello Express App!!',
18 | })
19 | })
20 |
21 | server.listen(5001, () => {
22 | console.log('listening on *:5001')
23 | })
24 |
--------------------------------------------------------------------------------
/recipe-docker-nginx-express/docker/api/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "api",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "start": "node index.js"
9 | },
10 | "keywords": [],
11 | "author": "",
12 | "license": "ISC",
13 | "dependencies": {
14 | "cors": "^2.8.5",
15 | "express": "^4.17.1"
16 | }
17 | }
--------------------------------------------------------------------------------
/recipe-docker-nginx-express/docker/nginx/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM nginx:latest
2 |
3 | # default.cof 書き換え
4 | COPY ./conf.d/default.conf /etc/nginx/conf.d/default.conf
--------------------------------------------------------------------------------
/recipe-docker-nginx-express/docker/nginx/conf.d/default.conf:
--------------------------------------------------------------------------------
1 | server {
2 | listen 80;
3 | server_name localhost;
4 | location / {
5 | proxy_set_header Host $host;
6 | proxy_set_header X-Real-IP $remote_addr;
7 | proxy_set_header X-Forwarded-Host $host;
8 | proxy_set_header X-Forwarded-Server $host;
9 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
10 | proxy_pass http://api:5001/;
11 | }
12 | }
--------------------------------------------------------------------------------
/recipe-express-socketio-redis/README.md:
--------------------------------------------------------------------------------
1 | # recipe-express-socketio-redis
2 |
3 | socket-io を使ったチャットを複数サーバから行うために redis を用いた例(この構築ではシングル構成)
4 |
5 | 
6 |
7 | ## run
8 |
9 | ```
10 | docker-compose up -d --build
11 | ```
12 |
13 | VSCode remote explorer から express-app に接続。ディレクトリは`/express-app/`を選択
14 |
15 | TERMINAL から`node main.js`を実行
16 |
17 | ブラウザで`localhost:5010`を開きチャットができれば正常に起動
18 |
--------------------------------------------------------------------------------
/recipe-express-socketio-redis/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hironomiu/Docker-DockerCompose-Training/2c88c7b56858ae4740a856de776b5f6a594ed343/recipe-express-socketio-redis/demo.gif
--------------------------------------------------------------------------------
/recipe-express-socketio-redis/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "3"
2 | services:
3 | express-app:
4 | build: docker/express-app
5 | tty: true
6 | ports:
7 | - "5003:5003"
8 | user: root
9 | depends_on:
10 | - redis
11 | redis:
12 | image: "redis:latest"
13 | tty: true
14 | expose:
15 | - "6379"
16 | ports:
17 | - "6379:6379"
18 |
--------------------------------------------------------------------------------
/recipe-express-socketio-redis/docker/express-app/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:latest
2 |
3 | ENV APP_PATH=/express-app
4 | RUN mkdir $APP_PATH
5 | WORKDIR $APP_PATH
6 |
7 | RUN npm init -y
8 | RUN npm install -y express socket.io-redis socket.io
9 | RUN apt-get update
10 | RUN apt-get install -y vim
11 |
12 | COPY index.html /$APP_PATH/
13 | COPY main.js /$APP_PATH/
--------------------------------------------------------------------------------
/recipe-express-socketio-redis/docker/express-app/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Socket.IO chat
6 |
62 |
63 |
64 |
65 |
66 |
69 |
70 |
92 |
93 |
94 |
--------------------------------------------------------------------------------
/recipe-express-socketio-redis/docker/express-app/main.js:
--------------------------------------------------------------------------------
1 | // express
2 | const express = require("express")
3 | const app = express()
4 | const http = require("http")
5 | const server = http.createServer(app)
6 |
7 | // socket-io + redis
8 | const redis = require("socket.io-redis")
9 | const { Server } = require("socket.io")
10 | const io = new Server(server)
11 | const HOST = "redis"
12 | const REDIS_PORT = 6379
13 | const WEB_PORT = 5010
14 |
15 | io.adapter(redis({ host: HOST, port: REDIS_PORT }))
16 |
17 | io.on("connection", (socket) => {
18 | socket.on("chat message", (msg) => {
19 | console.log("message: " + msg)
20 | io.emit("chat message", msg)
21 | })
22 | })
23 |
24 | app.get("/", (req, res) => {
25 | res.sendFile(__dirname + "/index.html")
26 | })
27 |
28 | server.listen(WEB_PORT, () => {
29 | console.log(`listening on *:${WEB_PORT}`)
30 | })
31 |
--------------------------------------------------------------------------------
/recipe-https-nginx-typescript-react-express-mysql/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # Created by https://www.toptal.com/developers/gitignore/api/macos,node,react
3 | # Edit at https://www.toptal.com/developers/gitignore?templates=macos,node,react
4 |
5 | cert
6 |
7 | ### macOS ###
8 | # General
9 | .DS_Store
10 | .AppleDouble
11 | .LSOverride
12 |
13 | # Icon must end with two \r
14 | Icon
15 |
16 |
17 | # Thumbnails
18 | ._*
19 |
20 | # Files that might appear in the root of a volume
21 | .DocumentRevisions-V100
22 | .fseventsd
23 | .Spotlight-V100
24 | .TemporaryItems
25 | .Trashes
26 | .VolumeIcon.icns
27 | .com.apple.timemachine.donotpresent
28 |
29 | # Directories potentially created on remote AFP share
30 | .AppleDB
31 | .AppleDesktop
32 | Network Trash Folder
33 | Temporary Items
34 | .apdisk
35 |
36 | ### Node ###
37 | # Logs
38 | logs
39 | *.log
40 | npm-debug.log*
41 | yarn-debug.log*
42 | yarn-error.log*
43 | lerna-debug.log*
44 | .pnpm-debug.log*
45 |
46 | # Diagnostic reports (https://nodejs.org/api/report.html)
47 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
48 |
49 | # Runtime data
50 | pids
51 | *.pid
52 | *.seed
53 | *.pid.lock
54 |
55 | # Directory for instrumented libs generated by jscoverage/JSCover
56 | lib-cov
57 |
58 | # Coverage directory used by tools like istanbul
59 | coverage
60 | *.lcov
61 |
62 | # nyc test coverage
63 | .nyc_output
64 |
65 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
66 | .grunt
67 |
68 | # Bower dependency directory (https://bower.io/)
69 | bower_components
70 |
71 | # node-waf configuration
72 | .lock-wscript
73 |
74 | # Compiled binary addons (https://nodejs.org/api/addons.html)
75 | build/Release
76 |
77 | # Dependency directories
78 | node_modules/
79 | jspm_packages/
80 |
81 | # Snowpack dependency directory (https://snowpack.dev/)
82 | web_modules/
83 |
84 | # TypeScript cache
85 | *.tsbuildinfo
86 |
87 | # Optional npm cache directory
88 | .npm
89 |
90 | # Optional eslint cache
91 | .eslintcache
92 |
93 | # Optional stylelint cache
94 | .stylelintcache
95 |
96 | # Microbundle cache
97 | .rpt2_cache/
98 | .rts2_cache_cjs/
99 | .rts2_cache_es/
100 | .rts2_cache_umd/
101 |
102 | # Optional REPL history
103 | .node_repl_history
104 |
105 | # Output of 'npm pack'
106 | *.tgz
107 |
108 | # Yarn Integrity file
109 | .yarn-integrity
110 |
111 | # dotenv environment variable files
112 | .env
113 | .env.development.local
114 | .env.test.local
115 | .env.production.local
116 | .env.local
117 |
118 | # parcel-bundler cache (https://parceljs.org/)
119 | .cache
120 | .parcel-cache
121 |
122 | # Next.js build output
123 | .next
124 | out
125 |
126 | # Nuxt.js build / generate output
127 | .nuxt
128 | dist
129 |
130 | # Gatsby files
131 | .cache/
132 | # Comment in the public line in if your project uses Gatsby and not Next.js
133 | # https://nextjs.org/blog/next-9-1#public-directory-support
134 | # public
135 |
136 | # vuepress build output
137 | .vuepress/dist
138 |
139 | # vuepress v2.x temp and cache directory
140 | .temp
141 |
142 | # Docusaurus cache and generated files
143 | .docusaurus
144 |
145 | # Serverless directories
146 | .serverless/
147 |
148 | # FuseBox cache
149 | .fusebox/
150 |
151 | # DynamoDB Local files
152 | .dynamodb/
153 |
154 | # TernJS port file
155 | .tern-port
156 |
157 | # Stores VSCode versions used for testing VSCode extensions
158 | .vscode-test
159 |
160 | # yarn v2
161 | .yarn/cache
162 | .yarn/unplugged
163 | .yarn/build-state.yml
164 | .yarn/install-state.gz
165 | .pnp.*
166 |
167 | ### Node Patch ###
168 | # Serverless Webpack directories
169 | .webpack/
170 |
171 | # Optional stylelint cache
172 |
173 | # SvelteKit build / generate output
174 | .svelte-kit
175 |
176 | ### react ###
177 | .DS_*
178 | **/*.backup.*
179 | **/*.back.*
180 |
181 | node_modules
182 |
183 | *.sublime*
184 |
185 | psd
186 | thumb
187 | sketch
188 |
189 | # End of https://www.toptal.com/developers/gitignore/api/macos,node,react
--------------------------------------------------------------------------------
/recipe-https-nginx-typescript-react-express-mysql/README.md:
--------------------------------------------------------------------------------
1 | # recipe-https-nginx-typescript-react-express-mysql
2 |
3 | HTTPS + Nginx + TypeScript + React + Express + MySQL の環境レシピ
4 |
5 | HTTPS は Nginx に証明書を設定し実現する
6 | React は build image を Nginx に volume の共有で認識させる
7 |
8 | ## 注意
9 |
10 | `docker/mysql/Dcokerfile`で`apt-get update`が現時点の latest だとエラーになるため以下を追記している。key でエラーになる場合などは適時変更削除すること
11 |
12 | ```
13 | RUN apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 467B942D3A79BD29
14 | ```
15 |
16 | ## 事前準備
17 |
18 | ### ドメイン(任意)
19 |
20 | 自己署名入り証明書ではなく正規のドメインからサブドメインなどに[Let’s Encrypt](https://letsencrypt.org/)で証明書を発行する場合は取得
21 |
22 | ### 証明書
23 |
24 | `./docker/nginx/`配下に`cert`ディレクトリを作成し、取得作成した`cert.crt`,`cert.key`を配置する(`cert`は`.gitignore`で除外されていることを確認する)
25 |
26 | ```
27 | mkdir ./docker/nginx/cert
28 | ```
29 |
30 | 独自ドメインに Let’ Encrypt にて証明書を取得する場合は[create-https-localhost-with-letsencrypt](https://github.com/hironomiu/create-https-localhost-with-letsencrypt)を参照
31 |
32 | その他[mkcert](https://github.com/FiloSottile/mkcert)を利用し作成する方法もある
33 |
34 | ## Build & Up
35 |
36 | ```
37 | docker-compose up --build -d
38 | ```
39 |
40 | ## 掃除
41 |
42 | ```
43 | docker-compose stop
44 | docker-compose rm
45 | docker image rm https-nginx
46 | docker image rm https-express-app
47 | docker image rm https-react-app
48 | docker image rm https-mysql
49 | docker volume rm https-express-app
50 | docker volume rm https-mysql
51 | docker volume rm https-react-app
52 | ```
53 |
54 | ## 動作確認用サンプルコード
55 |
56 | アプリの起動は`npx ts-node index.ts`で行う
57 |
58 | ### express-app
59 |
60 | `index.ts`
61 |
62 | ```
63 | import express,{ Request,Response } from 'express'
64 | import mysql from 'mysql2'
65 | import cors from 'cors'
66 |
67 | const app = express()
68 |
69 | const pool = mysql.createPool({
70 | connectionLimit: 5,
71 | host: 'db',
72 | user: 'appuser',
73 | password: 'mysql',
74 | database: 'test',
75 | })
76 |
77 | const promisePool = pool.promise()
78 |
79 | app.use(
80 | cors({
81 | // origin -> 作成したサブドメイン、Dcoker react-appコンテナ
82 | origin:['https://localhost.hironomiu.com','http://localhost:3333'],
83 | credentials:true,
84 | optionsSuccessStatus:200,
85 | })
86 | )
87 |
88 | app.get('/api/v1/hello', async (req:Request,res:Response) => {
89 | const [rows,fields]:[mysql.RowDataPacket[],any] = await promisePool.query('select 1 as num')
90 | if(rows[0]){
91 | res.json({message:`hello ${rows[0].num}`})
92 | }
93 | })
94 |
95 | app.listen(5550,() => {
96 | console.log(`express listen *:5550`)
97 | })
98 | ```
99 |
100 | ### react-app
101 |
102 | `.src/App.tsx`実装後`npm run build`を行い`./src/build`を作成する。これにより`https://作成したサブドメイン or https://localhost`にアクセスし`Nginx`経由で Build した React アプリにアクセスできる。
103 |
104 | ```
105 | import {FC ,useEffect,useState} from 'react';
106 |
107 | const App:FC = () => {
108 | const [data,setData] = useState({message:"hoge"})
109 | useEffect(()=> {
110 | ( async () => {
111 | // fetch先は作成したサブドメインに適時書き換える
112 | const res = await fetch('https://localhost.hironomiu.com/api/v1/hello')
113 | const data = await res.json()
114 | setData(data)
115 | })()
116 | })
117 | return {data.message}
;
118 | };
119 |
120 | export default App;
121 | ```
122 |
123 | ## Memo
124 |
125 | `./docker/nginx/Dockerfile` -> `RUN mkdir ${APP_PATH}`をコメントにしているが作成されない場合はコメントを外す
126 |
--------------------------------------------------------------------------------
/recipe-https-nginx-typescript-react-express-mysql/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 | services:
3 | nginx:
4 | build:
5 | context: ./docker/nginx
6 | dockerfile: Dockerfile
7 | image: https-nginx
8 | container_name: https-nginx
9 | ports:
10 | - 443:443
11 | volumes:
12 | - react-app:/react-app
13 | depends_on:
14 | - express-app
15 |
16 | react-app:
17 | build:
18 | context: ./docker/react-app
19 | dockerfile: Dockerfile
20 | tty: true
21 | image: https-react-app
22 | container_name: https-react-app
23 | ports:
24 | - "3333:3000"
25 | user: root
26 | volumes:
27 | - react-app:/react-app
28 |
29 | express-app:
30 | build:
31 | context: ./docker/express-app
32 | dockerfile: Dockerfile
33 | tty: true
34 | image: https-express-app
35 | container_name: https-express-app
36 | ports:
37 | - 5550:5550
38 | user: root
39 | volumes:
40 | - express-app:/express-app
41 |
42 | db:
43 | build: docker/mysql
44 | tty: true
45 | image: https-mysql
46 | container_name: https-mysql
47 | volumes:
48 | - mysql:/var/lib/mysql
49 | environment:
50 | MYSQL_DATABASE: test
51 | MYSQL_ROOT_PASSWORD: mysql
52 | MYSQL_USER: appuser
53 | MYSQL_PASSWORD: mysql
54 | expose:
55 | - '3306'
56 | ports:
57 | - '3309:3306'
58 |
59 | volumes:
60 | express-app:
61 | name: https-express-app
62 | react-app:
63 | name: https-react-app
64 | mysql:
65 | name: https-mysql
--------------------------------------------------------------------------------
/recipe-https-nginx-typescript-react-express-mysql/docker/express-app/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:latest
2 |
3 | ENV APP_PATH=/express-app
4 | RUN mkdir $APP_PATH
5 | WORKDIR $APP_PATH
6 |
7 | RUN npm init -y
8 | RUN npm install -y express cors pug mysql2
9 | RUN npm install -D typescript @types/node ts-node
10 | RUN npm install -D @types/express types/mysql2# @types/cors @types/pug
11 |
--------------------------------------------------------------------------------
/recipe-https-nginx-typescript-react-express-mysql/docker/mysql/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM mysql:latest
2 | ADD ./conf.d/my.cnf /etc/mysql/conf.d/my.cnf
3 | RUN apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 467B942D3A79BD29
4 | RUN apt-get update
5 | RUN apt-get install locales -y
6 | RUN sed -i -E 's/# (ja_JP.UTF-8)/\1/' /etc/locale.gen && locale-gen && update-locale LANG=ja_JP.UTF-8
7 | RUN echo "export LANG=ja_JP.UTF-8" >> ~/.bashrc
--------------------------------------------------------------------------------
/recipe-https-nginx-typescript-react-express-mysql/docker/mysql/conf.d/my.cnf:
--------------------------------------------------------------------------------
1 | [mysqld]
2 | innodb-buffer-pool-size=128M
3 | default-authentication-plugin=mysql_native_password
--------------------------------------------------------------------------------
/recipe-https-nginx-typescript-react-express-mysql/docker/nginx/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM nginx:latest
2 |
3 | ENV APP_PATH=/react-app
4 | # RUN mkdir ${APP_PATH}
5 |
6 | ENV SSL_PATH=/etc/nginx/ssl
7 | RUN mkdir ${SSL_PATH}
8 |
9 | COPY ./conf.d/default.conf /etc/nginx/conf.d/default.conf
10 | COPY ./cert/cert.crt /etc/nginx/ssl/cert.crt
11 | COPY ./cert/cert.key /etc/nginx/ssl/cert.key
--------------------------------------------------------------------------------
/recipe-https-nginx-typescript-react-express-mysql/docker/nginx/conf.d/default.conf:
--------------------------------------------------------------------------------
1 | server {
2 | listen 443 ssl;
3 | server_name localhost.hironomiu.com;
4 |
5 | ssl_certificate /etc/nginx/ssl/cert.crt;
6 | ssl_certificate_key /etc/nginx/ssl/cert.key;
7 |
8 | access_log /var/log/nginx/access.log;
9 | error_log /var/log/nginx/error.log;
10 |
11 | root /react-app/build;
12 | index index.html;
13 |
14 | location / {
15 | try_files $uri /index.html;
16 | }
17 |
18 | location /api/v1 {
19 | proxy_set_header Host $host;
20 | proxy_set_header X-Real-IP $remote_addr;
21 | proxy_set_header X-Forwarded-Host $host;
22 | proxy_set_header X-Forwarded-Server $host;
23 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
24 | proxy_pass http://express-app:5550/api/v1;
25 | }
26 | }
--------------------------------------------------------------------------------
/recipe-https-nginx-typescript-react-express-mysql/docker/react-app/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:latest
2 |
3 | ENV APP_PATH=/react-app
4 | RUN mkdir ${APP_PATH}
5 | WORKDIR ${APP_PATH}
6 |
7 | RUN npx create-react-app . --template typescript
--------------------------------------------------------------------------------
/recipe-https-nginx-typescript-react-express-mysql/sample/express-app/index.ts:
--------------------------------------------------------------------------------
1 | import express,{ Request,Response } from 'express'
2 | import mysql from 'mysql2'
3 | import cors from 'cors'
4 |
5 | const app = express()
6 |
7 | const pool = mysql.createPool({
8 | connectionLimit: 5,
9 | host: 'db',
10 | user: 'appuser',
11 | password: 'mysql',
12 | database: 'test',
13 | })
14 |
15 | const promisePool = pool.promise()
16 |
17 | app.use(
18 | cors({
19 | // origin -> 作成したサブドメイン、Dcoker react-appコンテナ
20 | origin:['https://localhost.hironomiu.com','http://localhost:3333'],
21 | credentials:true,
22 | optionsSuccessStatus:200,
23 | })
24 | )
25 |
26 | app.get('/api/v1/hello', async (req:Request,res:Response) => {
27 | const [rows,fields]:[mysql.RowDataPacket[],any] = await promisePool.query('select 1 as num')
28 | if(rows[0]){
29 | res.json({message:`hello ${rows[0].num}`})
30 | }
31 | })
32 |
33 | app.listen(5550,() => {
34 | console.log(`express listen *:5550`)
35 | })
--------------------------------------------------------------------------------
/recipe-https-nginx-typescript-react-express-mysql/sample/react-app/App.tsx:
--------------------------------------------------------------------------------
1 | import {FC ,useEffect,useState} from 'react';
2 |
3 | const App:FC = () => {
4 | const [data,setData] = useState({message:"hoge"})
5 | useEffect(()=> {
6 | ( async () => {
7 | // fetch先は作成したサブドメインに適時書き換える
8 | const res = await fetch('https://localhost.hironomiu.com/api/v1/hello')
9 | const data = await res.json()
10 | setData(data)
11 | })()
12 | })
13 | return {data.message}
;
14 | };
15 |
16 | export default App;
--------------------------------------------------------------------------------
/recipe-mysql-dockerfile/README.md:
--------------------------------------------------------------------------------
1 | # recipe-mysql-dockerfile
2 |
3 | Dockerfile を用いた MySQL の環境構築
4 |
5 | ## 事前準備
6 |
7 | [Docker](https://www.docker.com/)、[Docker Compose](https://docs.docker.com/compose/)が動作すること([Docker Desktop](https://www.docker.com/get-started)で可能)
8 |
9 | ## 構成
10 |
11 | - PORT
12 | - host 3306 : guest 3306
13 | - 作成 DB
14 | - test
15 | - アカウント
16 | - root mysql
17 | - appuser appuser
18 | - install tools
19 | - vim
20 |
21 | ## SetUp
22 |
23 | ### Dockerfile の配置
24 |
25 | 任意のディレクトリで以下のディレクトリを作成
26 |
27 | ```
28 | mkdir -p docker/mysql
29 | ```
30 |
31 | `docker/mysql`に[Dockerfile](./docker/mysql/Dockerfile)を作成
32 |
33 | ```
34 | FROM mysql:latest
35 | ADD ./conf.d/my.cnf /etc/mysql/conf.d/my.cnf
36 |
37 | ENV MYSQL_DATABASE=test
38 | ENV MYSQL_ROOT_PASSWORD=mysql
39 | ENV MYSQL_USER=appuser
40 | ENV MYSQL_PASSWORD=appuser
41 |
42 | RUN apt-get update
43 | RUN apt-get install -y vim
44 | RUN apt-get install -y locales
45 | RUN sed -i -E 's/# (ja_JP.UTF-8)/\1/' /etc/locale.gen
46 | RUN locale-gen
47 | RUN update-locale LANG=ja_JP.UTF-8
48 | RUN echo "export LANG=ja_JP.UTF-8" >> ~/.bashrc
49 |
50 | EXPOSE 3306
51 | ```
52 |
53 | `docker/mysql`に`conf.d`ディレクトリを作成
54 |
55 | ```
56 | mkdir conf.d
57 | ```
58 |
59 | `conf.d`配下に[my.cnf](./docker/mysql/conf.d/my.cnf)を作成
60 |
61 | ```
62 | [mysqld]
63 | innodb-buffer-pool-size=128M
64 | default-authentication-plugin=mysql_native_password
65 | ```
66 |
67 | ### images 作成
68 |
69 | ```
70 | docker image build -t mysql:latest ./docker/mysql/
71 | ```
72 |
73 | ### container 起動(初回)
74 |
75 | ```
76 | docker container run --name mysqld -d -p 3306:3306 mysql
77 | ```
78 |
79 | 注:m1 mac の場合 `--platform linux/x86_64` でエミュレートする
80 |
81 | ```
82 | docker container run --platform linux/x86_64 --name mysqld -d -p 3306:3306 mysql
83 | ```
84 |
85 | ## 接続
86 |
87 | パスワードは `mysql`
88 |
89 | ```
90 | docker container exec -it mysqld bash
91 |
92 | mysql -u root -p
93 |
94 | mysql>
95 | ```
96 |
97 | ローカルに mysql client がある場合
98 |
99 | ```
100 | mysql -u root -p -h127.0.0.1
101 |
102 | mysql>
103 | ```
104 |
105 | ## 注意
106 |
107 | `image`削除後は適時`volume`を削除
108 |
--------------------------------------------------------------------------------
/recipe-mysql-dockerfile/docker/mysql/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM mysql:latest
2 | ADD ./conf.d/my.cnf /etc/mysql/conf.d/my.cnf
3 |
4 | ENV MYSQL_DATABASE=test
5 | ENV MYSQL_ROOT_PASSWORD=mysql
6 | ENV MYSQL_USER=appuser
7 | ENV MYSQL_PASSWORD=appuser
8 |
9 | RUN apt-get update
10 | RUN apt-get install -y vim
11 | RUN apt-get install -y locales
12 | RUN sed -i -E 's/# (ja_JP.UTF-8)/\1/' /etc/locale.gen
13 | RUN locale-gen
14 | RUN update-locale LANG=ja_JP.UTF-8
15 | RUN echo "export LANG=ja_JP.UTF-8" >> ~/.bashrc
16 |
17 | EXPOSE 3306
--------------------------------------------------------------------------------
/recipe-mysql-dockerfile/docker/mysql/conf.d/my.cnf:
--------------------------------------------------------------------------------
1 | [mysqld]
2 | innodb-buffer-pool-size=128M
3 | default-authentication-plugin=mysql_native_password
--------------------------------------------------------------------------------
/recipe-mysql-no-dockerfile/README.md:
--------------------------------------------------------------------------------
1 | # recipe-mysql-no-dockerfile
2 |
3 | Dockerfile を用いずコマンドのみでの MySQL の環境構築
4 |
5 | ## 事前準備
6 |
7 | [Docker](https://www.docker.com/)、[Docker Compose](https://docs.docker.com/compose/)が動作すること([Docker Desktop](https://www.docker.com/get-started)で可能)
8 |
9 | ## すること
10 |
11 | 1. Docker で MySQL を起動し接続
12 |
13 | ## MysSQL
14 |
15 | ### イメージの確認
16 |
17 | ```
18 | $ docker image ls
19 | REPOSITORY TAG IMAGE ID CREATED SIZE
20 | ```
21 |
22 | ### run(イメージを取得しコンテナの起動)
23 |
24 | ```
25 | $ docker run --name mysqld -e MYSQL_ROOT_PASSWORD=mysql -d -p 3306:3306 mysql
26 | ```
27 |
28 | ### イメージの確認
29 |
30 | ```
31 | $ docker image ls
32 | REPOSITORY TAG IMAGE ID CREATED SIZE
33 | mysql latest c0cdc95609f1 13 days ago 556MB
34 | ```
35 |
36 | ### コンテナの確認
37 |
38 | ```
39 | $ docker container ls -a
40 | CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
41 | 88f10acb0dc0 mysql "docker-entrypoint.s…" 2 minutes ago Up 2 minutes 0.0.0.0:3306->3306/tcp, :::3306->3306/tcp, 33060/tcp mysqld
42 | ```
43 |
44 | ### bash モードでコンテナに接続
45 |
46 | ```
47 | $ docker container exec -it mysqld bash
48 | root@88f10acb0dc0:/#
49 | ```
50 |
51 | ※日本語入力を可能にするために`apt-get`,`~/.bashrc`に反映する。このターミナルで続けて作業をする場合は`source`をし反映させ作業を行う。(docker container start では以下は行わなくて良い)
52 |
53 | ```
54 | apt-get update && apt-get install locales -y && sed -i -E 's/# (ja_JP.UTF-8)/\1/' /etc/locale.gen && locale-gen && update-locale LANG=ja_JP.UTF-8 && echo "export LANG=ja_JP.UTF-8" >> ~/.bashrc
55 |
56 | source ~/.bashrc
57 | ```
58 |
59 | ### MySQL の操作(パスワードは`mysql`)
60 |
61 | ```
62 | root@88f10acb0dc0:/# mysql -u root -p
63 | Enter password:
64 | Welcome to the MySQL monitor. Commands end with ; or \g.
65 | Your MySQL connection id is 8
66 | Server version: 8.0.25 MySQL Community Server - GPL
67 |
68 | Copyright (c) 2000, 2021, Oracle and/or its affiliates.
69 |
70 | Oracle is a registered trademark of Oracle Corporation and/or its
71 | affiliates. Other names may be trademarks of their respective
72 | owners.
73 |
74 | Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
75 |
76 | mysql>
77 | mysql> exit
78 | Bye
79 | root@88f10acb0dc0:/#
80 | ```
81 |
82 | コンテナから exit
83 |
84 | ```
85 | root@88f10acb0dc0:/# exit
86 | exit
87 | $
88 | ```
89 |
90 | ローカルに MySQL Client がインストールされている場合(パスワードは`mysql`)
91 |
92 | ```
93 | $ mysql -u root -p -h127.0.0.1
94 | Enter password:
95 | Welcome to the MySQL monitor. Commands end with ; or \g.
96 | Your MySQL connection id is 9
97 | Server version: 8.0.25 MySQL Community Server - GPL
98 |
99 | Copyright (c) 2000, 2021, Oracle and/or its affiliates.
100 |
101 | Oracle is a registered trademark of Oracle Corporation and/or its
102 | affiliates. Other names may be trademarks of their respective
103 | owners.
104 |
105 | Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
106 |
107 | mysql>
108 | mysql> exit
109 | Bye
110 | $
111 | ```
112 |
113 | ## DB の作成
114 |
115 | MySQL 上に`test`DB の作成例
116 |
117 | bash モードでコンテナに接続し MySQL の接続(パスワードは`mysql`)
118 |
119 | ```
120 | $ docker container exec -it mysqld bash
121 | root@88f10acb0dc0:/# mysql -u root -p
122 | ```
123 |
124 | ローカルに MySQL Client がインストールされている場合(パスワードは`mysql`)の接続
125 |
126 | ```
127 | $ mysql -u root -p -h127.0.0.1
128 | ```
129 |
130 | `test`DB の作成
131 |
132 | ```
133 | mysql> create database test;
134 | Query OK, 1 row affected (0.12 sec)
135 | ```
136 |
137 | 確認
138 |
139 | ```
140 | mysql> show create database test;
141 | +----------+--------------------------------------------------------------------------------------------------------------------------------+
142 | | Database | Create Database |
143 | +----------+--------------------------------------------------------------------------------------------------------------------------------+
144 | | test | CREATE DATABASE `test` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci */ /*!80016 DEFAULT ENCRYPTION='N' */ |
145 | +----------+--------------------------------------------------------------------------------------------------------------------------------+
146 | 1 row in set (0.01 sec)
147 | ```
148 |
149 | `test`DB に遷移(具体的な SQL のオペレーションは遷移後に行う)
150 |
151 | ```
152 | mysql> use test
153 | Database changed
154 | mysql>
155 | ```
156 |
157 | 作業が終わったら EXIT
158 |
159 | ```
160 | mysql> exit
161 | ```
162 |
163 | ## 基本操作
164 |
165 | 確認
166 |
167 | ```
168 | $ docker container ls -a
169 | CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
170 | 88f10acb0dc0 mysql "docker-entrypoint.s…" 7 minutes ago Up 7 minutes 0.0.0.0:3306->3306/tcp, :::3306->3306/tcp, 33060/tcp mysqld
171 | ```
172 |
173 | 停止(と確認)
174 |
175 | ```
176 | $ docker container stop mysqld
177 | mysqld
178 |
179 | $ docker container ls -a
180 | CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
181 | 88f10acb0dc0 mysql "docker-entrypoint.s…" 8 minutes ago Exited (0) 11 seconds ago mysqld
182 | ```
183 |
184 | 起動(と確認)
185 |
186 | ```
187 | $ docker container start mysqld
188 | mysqld
189 |
190 | $ docker container ls -a
191 | CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
192 | 88f10acb0dc0 mysql "docker-entrypoint.s…" 8 minutes ago Up 2 seconds 0.0.0.0:3306->3306/tcp, :::3306->3306/tcp, 33060/tcp mysqld
193 | ```
194 |
--------------------------------------------------------------------------------
/recipe-nginx-react-express-mysql/README.md:
--------------------------------------------------------------------------------
1 | # recipe-nginx-react-express-mysql
2 |
3 | Nginx + React + Express + MySQL の環境レシピ
4 |
5 | React は build image を Nginx に volume の共有で認識させる
6 |
7 | ## Build & Up
8 |
9 | ```
10 | docker-compose up --build -d
11 | ```
12 |
13 | ## 掃除
14 |
15 | ```
16 | docker-compose stop
17 | docker-compose rm
18 | docker image rm nginx
19 | docker image rm express-app
20 | docker image rm react-app
21 | docker image rm compose-mysql
22 | docker volume rm recipe-nginx-react-express-mysql_express-app
23 | docker volume rm recipe-nginx-react-express-mysql_react-app
24 | docker volume rm recipe-nginx-react-express-mysql_mysql
25 | ```
26 |
27 | ## 動作確認用サンプルコード
28 |
29 | ### react-app
30 |
31 | `.src/App.js`実装後`yarn build`を行い`./src/build`が作成されること。これにより`Nginx http://localhost:8080`から React アプリをアクセスできる。
32 |
33 | ```
34 | import {useEffect} from 'react'
35 |
36 | const App = () => {
37 | useEffect(() => {
38 | (async() => {
39 | const res = await fetch('http://localhost:5500/api/v1/hello')
40 | const data = await res.json()
41 | console.log(data)
42 | })()
43 | },[])
44 | return (
45 |
46 | hello
47 |
48 | )
49 | }
50 |
51 | export default App
52 | ```
53 |
54 | ### express-app
55 |
56 | `index.js`
57 |
58 | ```
59 | const express = require('express')
60 | const mysql = require('mysql2')
61 | const cors = require('cors')
62 |
63 | const app = express()
64 |
65 | const pool = mysql.createPool({
66 | connectionLimit: 5,
67 | host: 'db',
68 | user: 'appuser',
69 | password: 'mysql',
70 | database: 'test',
71 | })
72 |
73 | const promisePool = pool.promise()
74 |
75 | app.use(
76 | cors({
77 | origin:'http://localhost:8080',
78 | credentials:true,
79 | optionsSuccessStatus:200,
80 | })
81 | )
82 |
83 | app.get('/api/v1/hello', async (req,res) => {
84 | const [rows,fields] = await promisePool.query('select 1 as num')
85 | res.json({message:`hello ${rows[0].num}`})
86 | })
87 |
88 | app.listen(5500,() => {
89 | console.log(`express listen *:5500`)
90 | })
91 | ```
92 |
93 | ## Memo コンテナ単位での起動
94 |
95 | ### 共有ボリューム(react-app)
96 |
97 | ```
98 | docker volume create react-app
99 | ```
100 |
101 | ### Nginx
102 |
103 | ```
104 | docker image build --file=./docker/nginx/Dockerfile -t nginx:1 ./docker/nginx
105 | ```
106 |
107 | ```
108 | docker container run -p 8080:80 --mount type=volume,src=react-app,dst=/react-app -it -d --name nginxd nginx:1
109 | ```
110 |
111 | ```
112 | docker container stop nginxd
113 | docker container rm nginxd
114 | docker image rm nginx:1
115 | docker volume rm react-app
116 | ```
117 |
118 | ### React
119 |
120 | ```
121 | docker image build --file=./docker/react-app/Dockerfile -t react-app:1 .
122 | ```
123 |
124 | ```
125 | docker container run -p 3555:3000 --mount type=volume,src=react-app,dst=/react-app -it -d --name react-app react-app:1
126 | ```
127 |
--------------------------------------------------------------------------------
/recipe-nginx-react-express-mysql/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 | services:
3 | nginx:
4 | build:
5 | context: ./docker/nginx
6 | dockerfile: Dockerfile
7 | image: nginx
8 | container_name: nginx
9 | ports:
10 | - 8080:80
11 | volumes:
12 | - react-app:/react-app
13 |
14 | react-app:
15 | build:
16 | context: ./docker/react-app
17 | dockerfile: Dockerfile
18 | tty: true
19 | container_name: react-app
20 | image: react-app
21 | ports:
22 | - "3555:3000"
23 | user: root
24 | volumes:
25 | - react-app:/react-app
26 |
27 | express-app:
28 | build:
29 | context: ./docker/express-app
30 | dockerfile: Dockerfile
31 | tty: true
32 | image: express-app
33 | container_name: express-app
34 | ports:
35 | - 5500:5500
36 | user: root
37 | volumes:
38 | - express-app:/express-app
39 |
40 | db:
41 | build: docker/mysql
42 | tty: true
43 | image: compose-mysql
44 | container_name: compose-mysql
45 | volumes:
46 | - mysql:/var/lib/mysql
47 | environment:
48 | MYSQL_DATABASE: test
49 | MYSQL_ROOT_PASSWORD: mysql
50 | MYSQL_USER: appuser
51 | MYSQL_PASSWORD: mysql
52 | expose:
53 | - '3306'
54 | ports:
55 | - '3308:3306'
56 |
57 | volumes:
58 | express-app:
59 | react-app:
60 | mysql:
--------------------------------------------------------------------------------
/recipe-nginx-react-express-mysql/docker/express-app/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:latest
2 |
3 | ENV APP_PATH=/express-app
4 | RUN mkdir $APP_PATH
5 | WORKDIR $APP_PATH
6 |
7 | RUN npm init -y
8 | RUN npm install -y express cors pug mysql2
9 |
--------------------------------------------------------------------------------
/recipe-nginx-react-express-mysql/docker/mysql/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM mysql:latest
2 | ADD ./conf.d/my.cnf /etc/mysql/conf.d/my.cnf
--------------------------------------------------------------------------------
/recipe-nginx-react-express-mysql/docker/mysql/conf.d/my.cnf:
--------------------------------------------------------------------------------
1 | [mysqld]
2 | innodb-buffer-pool-size=128M
3 | default-authentication-plugin=mysql_native_password
--------------------------------------------------------------------------------
/recipe-nginx-react-express-mysql/docker/nginx/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM nginx:latest
2 |
3 | ENV APP_PATH=/react-app
4 | RUN mkdir ${APP_PATH}
5 |
6 | COPY ./conf.d/default.conf /etc/nginx/conf.d/default.conf
--------------------------------------------------------------------------------
/recipe-nginx-react-express-mysql/docker/nginx/conf.d/default.conf:
--------------------------------------------------------------------------------
1 | server {
2 | listen 80;
3 | server_name localhost;
4 |
5 | access_log /var/log/nginx/access.log;
6 | error_log /var/log/nginx/error.log;
7 |
8 | root /react-app/build;
9 | index index.html;
10 |
11 | location / {
12 | try_files $uri /index.html;
13 | }
14 |
15 | location /api/v1 {
16 | proxy_set_header Host $host;
17 | proxy_set_header X-Real-IP $remote_addr;
18 | proxy_set_header X-Forwarded-Host $host;
19 | proxy_set_header X-Forwarded-Server $host;
20 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
21 | proxy_pass http://express-app:5500/api/v1;
22 | }
23 | }
--------------------------------------------------------------------------------
/recipe-nginx-react-express-mysql/docker/react-app/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:latest
2 |
3 | ENV APP_PATH=/react-app
4 | RUN mkdir ${APP_PATH}
5 | WORKDIR ${APP_PATH}
6 |
7 | RUN npx create-react-app .
--------------------------------------------------------------------------------
/recipe-postgresql-dockerfile/README.md:
--------------------------------------------------------------------------------
1 | # recipe-postgresql-dockerfile
2 |
3 | PostgreSQL 単体の環境
4 |
5 | ## 構成
6 |
7 | - PORT
8 | - host 5433 : guest 5432
9 | - 作成DB
10 | - test
11 | - アカウント、パスワード
12 | - postgres postgres
13 |
14 | ## build
15 |
16 | ```
17 | docker image build -t postgresql:latest ./docker/postgresql/
18 | ```
19 |
20 | ## run
21 |
22 | ```
23 | docker container run --name postgresql -d -p 5433:5432 postgresql
24 | ```
25 |
26 | ## 接続
27 |
28 | パスワードは `postgres`
29 |
30 | ```
31 | psql -U postgres -p 5433 -h 127.0.0.1
32 | ```
33 |
34 | ## 掃除
35 |
36 | ```
37 | docker container stop postgresql
38 | docker container rm postgresql
39 | docker image rm postgresql
40 | docker volume prune
41 | ```
--------------------------------------------------------------------------------
/recipe-postgresql-dockerfile/docker/postgresql/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM postgres:latest
2 |
3 | ENV POSTGRES_USER=postgres
4 | ENV POSTGRES_PASSWORD=postgres
5 | ENV POSTGRES_DB=test
6 | ENV TZ Asia/Tokyo
7 | ENV LANGUAGE ja_JP:ja
--------------------------------------------------------------------------------
/recipe-python-flask-blueprint/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by https://www.toptal.com/developers/gitignore/api/python,macos
2 | # Edit at https://www.toptal.com/developers/gitignore?templates=python,macos
3 |
4 | ### macOS ###
5 | # General
6 | .DS_Store
7 | .AppleDouble
8 | .LSOverride
9 |
10 | # Icon must end with two \r
11 | Icon
12 |
13 |
14 | # Thumbnails
15 | ._*
16 |
17 | # Files that might appear in the root of a volume
18 | .DocumentRevisions-V100
19 | .fseventsd
20 | .Spotlight-V100
21 | .TemporaryItems
22 | .Trashes
23 | .VolumeIcon.icns
24 | .com.apple.timemachine.donotpresent
25 |
26 | # Directories potentially created on remote AFP share
27 | .AppleDB
28 | .AppleDesktop
29 | Network Trash Folder
30 | Temporary Items
31 | .apdisk
32 |
33 | ### Python ###
34 | # Byte-compiled / optimized / DLL files
35 | __pycache__/
36 | *.py[cod]
37 | *$py.class
38 |
39 | # C extensions
40 | *.so
41 |
42 | # Distribution / packaging
43 | .Python
44 | build/
45 | develop-eggs/
46 | dist/
47 | downloads/
48 | eggs/
49 | .eggs/
50 | lib/
51 | lib64/
52 | parts/
53 | sdist/
54 | var/
55 | wheels/
56 | share/python-wheels/
57 | *.egg-info/
58 | .installed.cfg
59 | *.egg
60 | MANIFEST
61 |
62 | # PyInstaller
63 | # Usually these files are written by a python script from a template
64 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
65 | *.manifest
66 | *.spec
67 |
68 | # Installer logs
69 | pip-log.txt
70 | pip-delete-this-directory.txt
71 |
72 | # Unit test / coverage reports
73 | htmlcov/
74 | .tox/
75 | .nox/
76 | .coverage
77 | .coverage.*
78 | .cache
79 | nosetests.xml
80 | coverage.xml
81 | *.cover
82 | *.py,cover
83 | .hypothesis/
84 | .pytest_cache/
85 | cover/
86 |
87 | # Translations
88 | *.mo
89 | *.pot
90 |
91 | # Django stuff:
92 | *.log
93 | local_settings.py
94 | db.sqlite3
95 | db.sqlite3-journal
96 |
97 | # Flask stuff:
98 | instance/
99 | .webassets-cache
100 |
101 | # Scrapy stuff:
102 | .scrapy
103 |
104 | # Sphinx documentation
105 | docs/_build/
106 |
107 | # PyBuilder
108 | .pybuilder/
109 | target/
110 |
111 | # Jupyter Notebook
112 | .ipynb_checkpoints
113 |
114 | # IPython
115 | profile_default/
116 | ipython_config.py
117 |
118 | # pyenv
119 | # For a library or package, you might want to ignore these files since the code is
120 | # intended to run in multiple environments; otherwise, check them in:
121 | # .python-version
122 |
123 | # pipenv
124 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
125 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
126 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
127 | # install all needed dependencies.
128 | #Pipfile.lock
129 |
130 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
131 | __pypackages__/
132 |
133 | # Celery stuff
134 | celerybeat-schedule
135 | celerybeat.pid
136 |
137 | # SageMath parsed files
138 | *.sage.py
139 |
140 | # Environments
141 | .env
142 | .venv
143 | env/
144 | venv/
145 | ENV/
146 | env.bak/
147 | venv.bak/
148 |
149 | # Spyder project settings
150 | .spyderproject
151 | .spyproject
152 |
153 | # Rope project settings
154 | .ropeproject
155 |
156 | # mkdocs documentation
157 | /site
158 |
159 | # mypy
160 | .mypy_cache/
161 | .dmypy.json
162 | dmypy.json
163 |
164 | # Pyre type checker
165 | .pyre/
166 |
167 | # pytype static type analyzer
168 | .pytype/
169 |
170 | # Cython debug symbols
171 | cython_debug/
172 |
173 | # End of https://www.toptal.com/developers/gitignore/api/python,macos
--------------------------------------------------------------------------------
/recipe-python-flask-blueprint/README.md:
--------------------------------------------------------------------------------
1 | # recipe-python-flask-blueprint
2 |
3 | Docker で Flask の開発環境(Blueprint を用いたサンプルコード`sample`配下まで配置)の構築例
4 |
5 | ## 事前準備
6 |
7 | [Docker](https://www.docker.com/)、[Docker Compose](https://docs.docker.com/compose/)が動作すること([Docker Desktop](https://www.docker.com/get-started)で可能)
8 |
9 | ## Docker
10 |
11 | ### Dockerfile
12 |
13 | `docker/flask`配下にに配置
14 |
15 | ### build
16 |
17 | ```
18 | docker build --file=./docker/flask/Dockerfile -t flask-app:1 .
19 | ```
20 |
21 | ### create volume
22 |
23 | ```
24 | docker volume create flask-app
25 | ```
26 |
27 | ### run
28 |
29 | ```
30 | docker container run -p 5001:5001 --mount type=volume,src=flask-app,dst=/flask-app -it -d --name flask-app-1 flask-app:1
31 | ```
32 |
33 | ### 掃除
34 |
35 | ```
36 | docker container stop flask-app-1
37 | docker container rm flask-app-1
38 | docker volume rm flask-app
39 | docker image rm flask-app:1
40 | ```
41 |
42 | ## Flask
43 |
44 | ### アプリケーションの配置
45 |
46 | ルーティングによる呼び出しは Blueprint で分割しテンプレートは`/`のみ呼び出し他は API で構成する
47 |
48 | `sample/app`配下を`flask-app`に配置
49 |
50 | ### run
51 |
52 | `WORKDIR`直下で以下を実行
53 |
54 | ```
55 | export FLASK_APP=app
56 | export FLASK_ENV=development
57 | export FLASK_RUN_PORT=5001
58 | export FLASK_RUN_HOST=0.0.0.0
59 | flask run
60 | ```
61 |
--------------------------------------------------------------------------------
/recipe-python-flask-blueprint/docker/flask/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM python:latest
2 | USER root
3 |
4 | ENV APP_PATH=/flask-app
5 | ENV TEMPLATES_PATH=/flask-app/templates
6 | RUN mkdir $APP_PATH
7 | RUN mkdir $TEMPLATES_PATH
8 | ADD ./docker/flask/requirements.txt $APP_PATH
9 | ADD ./sample $APP_PATH
10 | WORKDIR $APP_PATH
11 |
12 | RUN pip install -r requirements.txt
--------------------------------------------------------------------------------
/recipe-python-flask-blueprint/docker/flask/requirements.txt:
--------------------------------------------------------------------------------
1 | flask
--------------------------------------------------------------------------------
/recipe-python-flask-blueprint/sample/app/__init__.py:
--------------------------------------------------------------------------------
1 | from flask import Flask ,jsonify,render_template
2 | from . import api
3 |
4 | def create_app():
5 | app = Flask(__name__)
6 | app.config['JSON_AS_ASCII'] = False
7 | app.register_blueprint(api.app)
8 |
9 | app.logger.info("create_app")
10 |
11 | @app.route('/')
12 | def index():
13 | app.logger.info("App /")
14 | return render_template('base.html',title='title',message="hello")
15 |
16 | return app
--------------------------------------------------------------------------------
/recipe-python-flask-blueprint/sample/app/api/__init__.py:
--------------------------------------------------------------------------------
1 | from flask import Blueprint
2 | app = Blueprint('api',__name__,url_prefix='/api')
3 |
4 | from . import users
5 | app.register_blueprint(users.app)
--------------------------------------------------------------------------------
/recipe-python-flask-blueprint/sample/app/api/users.py:
--------------------------------------------------------------------------------
1 | from flask import Blueprint,jsonify,current_app
2 |
3 | app = Blueprint('users', __name__, url_prefix='/users')
4 |
5 | @app.route('/',methods=['GET'])
6 | def users():
7 | logger = current_app.logger
8 | logger.info("App /api/users")
9 | return jsonify([{"name":"Bob"},{"name":"花子"},{"name": "太郎"}])
--------------------------------------------------------------------------------
/recipe-python-flask-blueprint/sample/app/templates/base.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Document
9 |
10 |
11 |
12 | {{ title }}
13 | {{ message }}
14 |
15 |
16 |
--------------------------------------------------------------------------------
/recipe-rails-postgresql/README.md:
--------------------------------------------------------------------------------
1 | # recipe-rails-postgresql
2 |
3 | rails + PostgreSQL の環境
4 |
5 | ## docker-compose
6 |
7 | ### build & up
8 |
9 | ```
10 | docker-compose up --build -d
11 | ```
12 |
13 | ### 起動
14 |
15 | ```
16 | docker-compose start
17 | ```
18 |
19 | ### 停止
20 |
21 | ```
22 | docker-compose stop
23 | ```
24 |
25 | ### rails(VSCode Remote Explorerで接続)
26 |
27 | `/rails-app/sample`で実行
28 |
29 | #### server 起動
30 |
31 | ```
32 | rails s -p 5555 -b 0.0.0.0
33 | ```
34 |
35 | #### コントローラー、モデル、マイグレーションサンプル
36 |
37 | 実行後ブラウザで`localhost:5555/users/new`を開きエンドポイントが作成されていることを確認
38 |
39 | ```
40 | rails generate controller Users new
41 | rails generate model User name:string email:string
42 | bundle exec rake db:migrate
43 | ```
44 |
45 | ### Postgresql(VSCode Remote Explorerで接続)
46 | パスワードは`postgres`、selectの結果が返る場合はrailsからのマイグレーションがPostgresqlに対して成功
47 |
48 | ```
49 | psql -U postgres -d test
50 |
51 | select * from users;
52 | ```
53 |
54 | ### 掃除
55 |
56 | ```
57 | docker-compose stop
58 | docker-compose rm
59 | docker image rm recipe-rails-postgresql_rails
60 | docker image rm recipe-rails-postgresql_db
61 | docker volume prune
62 | ```
63 |
64 | ## docker
65 | 個別にdocker containerを立ち上げる場合は以下
66 |
67 | ### rails
68 |
69 | #### buid & run
70 |
71 | ```
72 | docker image build --file=./docker/rails/Dockerfile -t rails:latest ./docker/rails/
73 | docker container run --name rails -it -d -p 5555:5555 rails
74 | ```
75 |
76 | #### 掃除
77 | ```
78 | docker container stop rails
79 | docker container rm rails
80 | docker image rm rails
81 | ```
82 |
83 | ### postgresql
84 |
85 | #### buid & run
86 |
87 | ```
88 | docker image build -t postgresql:latest ./docker/postgresql/
89 | docker container run --name postgresql -d -p 5432:5432 postgresql
90 | ```
91 |
92 | #### 掃除
93 | ```
94 | docker container stop postgresql
95 | docker container rm postgresql
96 | docker image rm postgresql
97 | docker volume prune
98 | ```
--------------------------------------------------------------------------------
/recipe-rails-postgresql/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 | services:
3 | rails:
4 | build: docker/rails
5 | tty: true
6 | ports:
7 | - '5555:5555'
8 | user: root
9 | depends_on:
10 | - db
11 | db:
12 | build: docker/postgresql
13 | tty: true
14 | ports:
15 | - '5432:5432'
--------------------------------------------------------------------------------
/recipe-rails-postgresql/docker/postgresql/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM postgres:latest
2 |
3 | ENV POSTGRES_USER=postgres
4 | ENV POSTGRES_PASSWORD=postgres
5 | ENV POSTGRES_DB=test
6 | ENV TZ Asia/Tokyo
7 | ENV LANGUAGE ja_JP:ja
--------------------------------------------------------------------------------
/recipe-rails-postgresql/docker/rails/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ruby:latest
2 | USER root
3 |
4 | RUN curl https://deb.nodesource.com/setup_12.x | bash
5 | RUN curl https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
6 | RUN echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list
7 | RUN apt-get update -qq && apt-get install -y nodejs yarn postgresql-client
8 | ENV APP_PATH=/rails-app
9 | RUN mkdir $APP_PATH
10 | WORKDIR $APP_PATH
11 |
12 | COPY Gemfile $APP_PATH/Gemfile
13 | COPY Gemfile.lock $APP_PATH/Gemfile.lock
14 | RUN bundle install
15 | RUN rails new sample --force --no-deps --database=postgresql
16 | COPY database.yml $APP_PATH/sample/config/database.yml
17 |
--------------------------------------------------------------------------------
/recipe-rails-postgresql/docker/rails/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 | gem 'rails', '~> 6'
--------------------------------------------------------------------------------
/recipe-rails-postgresql/docker/rails/Gemfile.lock:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hironomiu/Docker-DockerCompose-Training/2c88c7b56858ae4740a856de776b5f6a594ed343/recipe-rails-postgresql/docker/rails/Gemfile.lock
--------------------------------------------------------------------------------
/recipe-rails-postgresql/docker/rails/database.yml:
--------------------------------------------------------------------------------
1 | default: &default
2 | adapter: postgresql
3 | encoding: unicode
4 | host: db
5 | username: postgres
6 | password: postgres
7 | pool: 5
8 |
9 | development:
10 | <<: *default
11 | database: test
12 |
13 | test:
14 | <<: *default
15 | database: test
16 |
--------------------------------------------------------------------------------
/recipe-volume-share/README.md:
--------------------------------------------------------------------------------
1 | # recipe-volume-share
2 |
3 | volume`share`を共有するサンプル(nginx,express-app で共有)
4 |
--------------------------------------------------------------------------------
/recipe-volume-share/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 | services:
3 | nginx:
4 | build:
5 | context: ./docker/nginx
6 | dockerfile: Dockerfile
7 | image: nginx
8 | container_name: nginx
9 | ports:
10 | - 8888:80
11 | volumes:
12 | - share:/sahre
13 |
14 | express-app:
15 | build:
16 | context: ./docker/express-app
17 | dockerfile: Dockerfile
18 | tty: true
19 | image: express-app
20 | container_name: express-app
21 | ports:
22 | - 5001:5001
23 | user: root
24 | volumes:
25 | - express-app:/express-app
26 | - share:/share
27 | command: ['npm', 'start']
28 | volumes:
29 | express-app:
30 | share:
31 |
--------------------------------------------------------------------------------
/recipe-volume-share/docker/express-app/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:latest
2 |
3 | ENV APP_PATH=/express-app
4 | RUN mkdir $APP_PATH
5 | WORKDIR $APP_PATH
6 | ENV SHARE_PATH=/share
7 | RUN mkdir $SHARE_PATH
8 |
9 | COPY package.json /express-app/
10 | COPY index.js /express-app/
11 |
12 | RUN npm install
--------------------------------------------------------------------------------
/recipe-volume-share/docker/express-app/index.js:
--------------------------------------------------------------------------------
1 | const express = require('express')
2 | const app = express()
3 | const cors = require('cors')
4 | const http = require('http')
5 | const server = http.createServer(app)
6 |
7 | app.use(
8 | cors({
9 | origin: 'http://localhost:3000',
10 | credentials: true,
11 | optionsSuccessStatus: 200,
12 | })
13 | )
14 |
15 | app.get('/', (req, res) => {
16 | res.json({
17 | message: 'Hello Express App!!',
18 | })
19 | })
20 | server.listen(5001, () => {
21 | console.log('listening on *:5001')
22 | })
23 |
--------------------------------------------------------------------------------
/recipe-volume-share/docker/express-app/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "api",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "start": "node index.js"
9 | },
10 | "keywords": [],
11 | "author": "",
12 | "license": "ISC",
13 | "dependencies": {
14 | "cors": "^2.8.5",
15 | "express": "^4.17.1"
16 | }
17 | }
--------------------------------------------------------------------------------
/recipe-volume-share/docker/nginx/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM nginx:latest
2 |
3 | ENV SHARE_PATH=/share
4 | RUN mkdir $SHARE_PATH
5 | WORKDIR $SHARE_PATH
6 |
7 | # default.cof 書き換え
8 | COPY ./conf.d/default.conf /etc/nginx/conf.d/default.conf
--------------------------------------------------------------------------------
/recipe-volume-share/docker/nginx/conf.d/default.conf:
--------------------------------------------------------------------------------
1 | server {
2 | listen 80;
3 | server_name localhost;
4 | location / {
5 | proxy_set_header Host $host;
6 | proxy_set_header X-Real-IP $remote_addr;
7 | proxy_set_header X-Forwarded-Host $host;
8 | proxy_set_header X-Forwarded-Server $host;
9 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
10 | proxy_pass http://express-app:5001/;
11 | }
12 | }
--------------------------------------------------------------------------------
/recipe-vscode-git-github-https/README.md:
--------------------------------------------------------------------------------
1 | # VSCode + Git & GitHub + https
2 |
3 | VSCode Remote Explorer から Docker コンテナに接続した際に VSCode の機能で Git & GitHub を https で扱う例
4 |
5 | ターミナルを開く
6 |
7 | `git init`
8 |
9 | ```
10 | # git init
11 | ```
12 |
13 | `config`の設定
14 |
15 | ```
16 | # git config --global user.name hironomiu
17 | # git config --global user.email hironomiu@gmail.com
18 | # git config --global init.defaultBranch main
19 | ```
20 |
21 | コードの add commit
22 |
23 | ```
24 | # git add .
25 | # git commit -m "commited"
26 | ```
27 |
28 | リモートリポジトリの設定(今回は https で実行)
29 |
30 | ```
31 | # git remote add origin https://github.com/hironomiu/xxxx.git
32 | # git branch -M main
33 | # git push -u origin main
34 | ```
35 |
36 | `git push`を初回行うと VSCode から認証を聞かれるので指示通り進む
37 |
38 | 
39 |
40 | `Continue` を押下
41 |
42 | 
43 |
44 | `Authorize github`を押下
45 |
46 | 
47 |
48 | GitHub アカウントのパスワードを入力し`Confirm password`を押下
49 |
50 | 
51 |
52 | 対象のリポジトリに push が成功していることを確認(以降は設定されているのでこのオペレーションは不要)
53 |
54 | 
55 |
--------------------------------------------------------------------------------
/recipe-vscode-git-github-https/images/git-00.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hironomiu/Docker-DockerCompose-Training/2c88c7b56858ae4740a856de776b5f6a594ed343/recipe-vscode-git-github-https/images/git-00.png
--------------------------------------------------------------------------------
/recipe-vscode-git-github-https/images/git-01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hironomiu/Docker-DockerCompose-Training/2c88c7b56858ae4740a856de776b5f6a594ed343/recipe-vscode-git-github-https/images/git-01.png
--------------------------------------------------------------------------------
/recipe-vscode-git-github-https/images/git-02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hironomiu/Docker-DockerCompose-Training/2c88c7b56858ae4740a856de776b5f6a594ed343/recipe-vscode-git-github-https/images/git-02.png
--------------------------------------------------------------------------------
/recipe-vscode-git-github-https/images/git-03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hironomiu/Docker-DockerCompose-Training/2c88c7b56858ae4740a856de776b5f6a594ed343/recipe-vscode-git-github-https/images/git-03.png
--------------------------------------------------------------------------------
/recipe-vscode-git-github-https/images/git-04.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hironomiu/Docker-DockerCompose-Training/2c88c7b56858ae4740a856de776b5f6a594ed343/recipe-vscode-git-github-https/images/git-04.png
--------------------------------------------------------------------------------
/tips/README.md:
--------------------------------------------------------------------------------
1 | # tips
2 |
3 | [MySQL コンテナのバックアップリストア](./mysql-backup-restore/)
4 |
5 | [Docker Network 機能によるアプリと DB の疎通](./network-node-mysql/)
6 |
--------------------------------------------------------------------------------
/tips/mysql-backup-restore/README.md:
--------------------------------------------------------------------------------
1 | # mysql-backup-restore
2 |
3 | MySQL コンテナのバックアップリストア
4 |
5 | ## ボリュームの作成
6 |
7 | ```
8 | docker volume create mysql-volume
9 | ```
10 |
11 | ## MySQL コンテナの起動
12 |
13 | ```
14 | docker run --name mysqld -dit -v mysql-volume:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=mysql mysql:latest
15 | ```
16 |
17 | ## リストア時に確認用の DB,テーブルの作成、データの投入
18 |
19 | ### コンテナ接続
20 |
21 | ```
22 | docker container exec -it mysqld bash
23 | ```
24 |
25 | ### MySQL 接続
26 |
27 | MySQL Client で接続。パスワードは`mysql`
28 |
29 | ```
30 | mysql -p
31 | ```
32 |
33 | ### DB、テーブルの作成、データの挿入
34 |
35 | DB の作成
36 |
37 | ```
38 | create database sample;
39 | ```
40 |
41 | 作成した DB(sample)に遷移
42 |
43 | ```
44 | use sample
45 | ```
46 |
47 | テーブルの作成、データの挿入
48 |
49 | ```
50 | create table users ( id int not null auto_increment ,name varchar(20) not null,primary key(id));
51 | insert into users(name) values("taro");
52 | ```
53 |
54 | 挿入したデータの確認
55 |
56 | ```
57 | select * from users;
58 |
59 | +----+------+
60 | | id | name |
61 | +----+------+
62 | | 1 | taro |
63 | +----+------+
64 | 1 row in set (0.00 sec)
65 | ```
66 |
67 | ### MySQL から抜ける
68 |
69 | ```
70 | exit
71 | ```
72 |
73 | ## コンテナから抜ける
74 |
75 | ```
76 | exit
77 | ```
78 |
79 | ## バックアップ
80 |
81 | MySQL コンテナは無停止(稼働中)だが挿入中などは未検証(基本はクラッシュリカバリなどでリカバリ時は対応されると思う)
82 |
83 | ```
84 | docker run --rm -v mysql-volume:/src -v "$PWD":/dest busybox tar czvf /dest/backup.tar.gz -C /src .
85 | ```
86 |
87 | カレントディレクトリに`backup.tar.gz`が作成されていること
88 |
89 | ```
90 | $ ll
91 | -rw-r--r-- 1 root root 11429209 Mar 6 09:11 backup.tar.gz
92 | ```
93 |
94 | ## リストア準備
95 |
96 | コンテナの停止、削除、イメージの削除、ボリュームの削除
97 |
98 | ```
99 | docker container stop mysqld
100 | docker container rm mysqld
101 | docker image rm mysql:latest
102 | docker volume rm mysql-volume
103 | ```
104 |
105 | ## リストア
106 |
107 | ボリュームを作成
108 |
109 | ```
110 | docker volume create mysql-volume
111 | ```
112 |
113 | 作成したボリュームにリストア
114 |
115 | ```
116 | docker run --rm -v mysql-volume:/dest -v "$PWD":/src busybox tar xzf /src/backup.tar.gz -C /dest .
117 | ```
118 |
119 | ## リストア確認
120 |
121 | コンテナを起動
122 |
123 | ```
124 | docker run --name mysqld -dit -v mysql-volume:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=mysql mysql:latest
125 | ```
126 |
127 | コンテナに接続
128 |
129 | ```
130 | docker container exec -it mysqld bash
131 | ```
132 |
133 | MySQL Client で接続。パスワードは`mysql`
134 |
135 | ```
136 | mysql -p
137 | use sample
138 | ```
139 |
140 | データの確認
141 |
142 | ```
143 | select * from users;
144 |
145 | +----+------+
146 | | id | name |
147 | +----+------+
148 | | 1 | taro |
149 | +----+------+
150 | 1 row in set (0.00 sec)
151 | ```
152 |
--------------------------------------------------------------------------------
/tips/network-node-mysql/README.md:
--------------------------------------------------------------------------------
1 | # network-node-mysql
2 |
3 | docker の network 機能を用いてコンテナ間の通信の設定サンプル。 具体的には node(`node-app`)コンテナと mysql(`mysqld`)コンテナをコンテナ名で通信できるよう設定する
4 |
5 | ## ネットワークの作成
6 |
7 | ```
8 | docker network create nodeMysql
9 | ```
10 |
11 | 確認(`nodeMysql`が`bridge`で作成されていること)
12 |
13 | ```
14 | docker network ls
15 | ```
16 |
17 | ## ボリュームの作成
18 |
19 | ```
20 | docker volume create node-app
21 | docker volume create mysql-volume
22 | ```
23 |
24 | ## コンテナの作成&起動
25 |
26 | `node`アプリコンテナ
27 |
28 | ```
29 | docker run -dit --name node-app -p 8082:8082 -v node-app:/node-app --net nodeMysql node:latest
30 | ```
31 |
32 | `mysql`コンテナ
33 |
34 | ```
35 | docker run --name mysqld -dit -v mysql-volume:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=mysql --net nodeMysql mysql:latest
36 | ```
37 |
38 | ## コンテナの起動確認
39 |
40 | Up していることを確認
41 |
42 | ```
43 | docker container ls -a
44 |
45 | CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
46 | 1f9584824fe0 mysql:latest "docker-entrypoint.s…" 37 seconds ago Up 37 seconds 3306/tcp, 33060/tcp mysqld
47 | cffcd4960e09 node:latest "docker-entrypoint.s…" About a minute ago Up About a minute 0.0.0.0:8082->8082/tcp, :::8082->8082/tcp node-app
48 | ```
49 |
50 | ## ネットワークの確認
51 |
52 | `nodeMysql`が存在すること
53 |
54 | `node-app`
55 |
56 | ```
57 | docker container inspect node-app --format="{{.NetworkSettings.Networks.nodeMysql}}"
58 | ```
59 |
60 | `mysqld`
61 |
62 | ```
63 | docker container inspect mysqld --format="{{.NetworkSettings.Networks.nodeMysql}}"
64 | ```
65 |
66 | ### 接続し express でアプリの作成
67 |
68 | node-app に接続
69 |
70 | ```
71 | docker container exec -it node-app bash
72 | ```
73 |
74 | ワークディレクトリに遷移し今回利用するパッケージ`express`,`mysql2`のインストール
75 |
76 | ```
77 | cd node-app
78 | npm install express mysql2
79 | ```
80 |
81 | コード編集用に`vim`をインストール
82 |
83 | ```
84 | apt update
85 | apt install -y vim
86 | ```
87 |
88 | vi で`index.js`を作成
89 |
90 | ```
91 | const express = require('express')
92 |
93 | const app = express()
94 |
95 | app.get('/',(req,res) => {
96 | res.json({message:'hello!'})
97 | })
98 |
99 | app.listen(8082,() => {
100 | console.log('listening on *:8082')
101 | })
102 | ```
103 |
104 | ## アプリを起動
105 |
106 | ```
107 | node index.js
108 | ```
109 |
110 | ## ブラウザで確認
111 |
112 | `losalhost:8082`にアクセスし`{"message":"hello!"}`が表示されること
113 |
114 | 表示後アプリを CTRL + C で停止しコンテナから抜ける
115 |
116 | ```
117 | exit
118 | ```
119 |
120 | ## MySQL にテーブルの作成
121 |
122 | MySQL コンテナに接続
123 |
124 | ```
125 | docker container exec -it mysqld bash
126 | ```
127 |
128 | MySQL Client から接続。パスワードは`mysql`
129 |
130 | ```
131 | mysql -p
132 | ```
133 |
134 | DB の作成
135 |
136 | ```
137 | create database test;
138 | ```
139 |
140 | DB を抜ける
141 |
142 | ```
143 | exit
144 | ```
145 |
146 | コンテナを抜ける
147 |
148 | ```
149 | exit
150 | ```
151 |
152 | ## アプリの修正
153 |
154 | `node-app`コンテナに接続
155 |
156 | ```
157 | docker container exec -it node-app bash
158 | ```
159 |
160 | アプリのホームディレクトリに遷移
161 |
162 | ```
163 | cd node-app
164 | ```
165 |
166 | MySQL にアクセスするよう`index.js`を修正
167 |
168 | ```
169 | const express = require('express')
170 | const mysql2 = require('mysql2')
171 |
172 | const app = express()
173 |
174 | const pool = mysql2.createPool({
175 | host:'mysqld',
176 | user:'root',
177 | password:'mysql',
178 | database:'test',
179 | })
180 | const promisePool = pool.promise()
181 |
182 | app.get('/',async (req,res) => {
183 | const [rows,fields] = await promisePool.query('select 1 as num')
184 | res.json(rows)
185 | })
186 |
187 | app.listen(8082,() => {
188 | console.log('listening on *:8082')
189 | })
190 | ```
191 |
192 | ## アプリの起動
193 |
194 | ```
195 | node index.js
196 | ```
197 |
198 | ## ブラウザで動作確認
199 |
200 | `losalhost:8082`にアクセスし`[{"num":1}]`が表示されること
201 |
202 | 表示後アプリを CTRL + C で停止しコンテナから抜ける
203 |
204 | ```
205 | exit
206 | ```
207 |
208 | ## Docker Network の切り離し、追加
209 |
210 | `node-app`に接続しアプリの起動
211 |
212 | ```
213 | docker container exec -it node-app bash
214 | cd node-app
215 | node index.js
216 | ```
217 |
218 | ### mysqld をネットワークから切り離し
219 |
220 | MySQL コンテナをネットワーク`node-mysql`から切り離す。切り離し後の確認は``となること
221 |
222 | ```
223 | docker container inspect mysqld --format="{{.NetworkSettings.Networks.nodeMysql}}"
224 | docker network disconnect nodeMysql mysqld
225 | docker container inspect mysqld --format="{{.NetworkSettings.Networks.nodeMysql}}"
226 | ```
227 |
228 | ### ブラウザで動作確認
229 |
230 | エラーとなること(起動している`node-app`コンテナでエラーメッセージが出力されること)
231 |
232 | ### node-app の起動
233 |
234 | ```
235 | node index.js
236 | ```
237 |
238 | ### mysqld にネットワークの追加
239 |
240 | MySQL コンテナをネットワーク`node-mysql`に追加する。追加後の確認は値か返ること
241 |
242 | ```
243 | docker container inspect mysqld --format="{{.NetworkSettings.Networks.nodeMysql}}"
244 | docker network connect nodeMysql mysqld
245 | docker container inspect mysqld --format="{{.NetworkSettings.Networks.nodeMysql}}"
246 | ```
247 |
248 | ### ブラウザで動作確認
249 |
250 | `losalhost:8082`にアクセスし`[{"num":1}]`が表示されること
251 |
252 | 表示後アプリを CTRL + C で停止しコンテナから抜ける
253 |
254 | ```
255 | exit
256 | ```
257 |
258 | ## 掃除
259 |
260 | ```
261 | docker container stop node-app
262 | docker container rm node-app
263 | docker image rm node:latest
264 | docker container stop mysqld
265 | docker container rm mysqld
266 | docker image rm mysql:latest
267 | docker volume rm node-app
268 | docker volume rm mysql-volume
269 | docker network rm nodeMysql
270 | ```
271 |
--------------------------------------------------------------------------------