81 | );
82 | }
83 | }
84 |
85 | export default App;
86 |
--------------------------------------------------------------------------------
/front/src/registerServiceWorker.js:
--------------------------------------------------------------------------------
1 | // In production, we register a service worker to serve assets from local cache.
2 |
3 | // This lets the app load faster on subsequent visits in production, and gives
4 | // it offline capabilities. However, it also means that developers (and users)
5 | // will only see deployed updates on the "N+1" visit to a page, since previously
6 | // cached resources are updated in the background.
7 |
8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
9 | // This link also includes instructions on opting out of this behavior.
10 |
11 | const isLocalhost = Boolean(
12 | window.location.hostname === 'localhost' ||
13 | // [::1] is the IPv6 localhost address.
14 | window.location.hostname === '[::1]' ||
15 | // 127.0.0.1/8 is considered localhost for IPv4.
16 | window.location.hostname.match(
17 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
18 | )
19 | );
20 |
21 | export default function register() {
22 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
23 | // The URL constructor is available in all browsers that support SW.
24 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location);
25 | if (publicUrl.origin !== window.location.origin) {
26 | // Our service worker won't work if PUBLIC_URL is on a different origin
27 | // from what our page is served on. This might happen if a CDN is used to
28 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374
29 | return;
30 | }
31 |
32 | window.addEventListener('load', () => {
33 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
34 |
35 | if (isLocalhost) {
36 | // This is running on localhost. Lets check if a service worker still exists or not.
37 | checkValidServiceWorker(swUrl);
38 |
39 | // Add some additional logging to localhost, pointing developers to the
40 | // service worker/PWA documentation.
41 | navigator.serviceWorker.ready.then(() => {
42 | console.log(
43 | 'This web app is being served cache-first by a service ' +
44 | 'worker. To learn more, visit https://goo.gl/SC7cgQ'
45 | );
46 | });
47 | } else {
48 | // Is not local host. Just register service worker
49 | registerValidSW(swUrl);
50 | }
51 | });
52 | }
53 | }
54 |
55 | function registerValidSW(swUrl) {
56 | navigator.serviceWorker
57 | .register(swUrl)
58 | .then(registration => {
59 | registration.onupdatefound = () => {
60 | const installingWorker = registration.installing;
61 | installingWorker.onstatechange = () => {
62 | if (installingWorker.state === 'installed') {
63 | if (navigator.serviceWorker.controller) {
64 | // At this point, the old content will have been purged and
65 | // the fresh content will have been added to the cache.
66 | // It's the perfect time to display a "New content is
67 | // available; please refresh." message in your web app.
68 | console.log('New content is available; please refresh.');
69 | } else {
70 | // At this point, everything has been precached.
71 | // It's the perfect time to display a
72 | // "Content is cached for offline use." message.
73 | console.log('Content is cached for offline use.');
74 | }
75 | }
76 | };
77 | };
78 | })
79 | .catch(error => {
80 | console.error('Error during service worker registration:', error);
81 | });
82 | }
83 |
84 | function checkValidServiceWorker(swUrl) {
85 | // Check if the service worker can be found. If it can't reload the page.
86 | fetch(swUrl)
87 | .then(response => {
88 | // Ensure service worker exists, and that we really are getting a JS file.
89 | if (
90 | response.status === 404 ||
91 | response.headers.get('content-type').indexOf('javascript') === -1
92 | ) {
93 | // No service worker found. Probably a different app. Reload the page.
94 | navigator.serviceWorker.ready.then(registration => {
95 | registration.unregister().then(() => {
96 | window.location.reload();
97 | });
98 | });
99 | } else {
100 | // Service worker found. Proceed as normal.
101 | registerValidSW(swUrl);
102 | }
103 | })
104 | .catch(() => {
105 | console.log(
106 | 'No internet connection found. App is running in offline mode.'
107 | );
108 | });
109 | }
110 |
111 | export function unregister() {
112 | if ('serviceWorker' in navigator) {
113 | navigator.serviceWorker.ready.then(registration => {
114 | registration.unregister();
115 | });
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | UTILS := $(shell dirname $(abspath $(lastword $(MAKEFILE_LIST))))/.makefile-utils.sh
2 |
3 | OK_COLOR = \033[0;32m
4 | NO_COLOR = \033[m
5 |
6 | DOCKER_USER = topheman
7 | DOCKER_IMAGE_PREFIX = $(DOCKER_USER)/docker-experiments
8 | DOCKER_IMAGE_NAME_FRONT_DEV = $(DOCKER_IMAGE_PREFIX)_front_development
9 | DOCKER_IMAGE_NAME_API_DEV = $(DOCKER_IMAGE_PREFIX)_api_development
10 | DOCKER_IMAGE_NAME_API_PROD = $(DOCKER_IMAGE_PREFIX)_api_production
11 | DOCKER_IMAGE_NAME_NGINX = $(DOCKER_IMAGE_PREFIX)_nginx
12 |
13 | TAG_LATEST = latest
14 | TAG ?= 1.0.1
15 |
16 | # development docker-compose
17 | COMPOSE = docker-compose
18 | COMPOSE_RUN = $(COMPOSE) run --rm
19 | COMPOSE_RUNCI = $(COMPOSE_RUN) -e CI=true
20 | COMPOSE_RUN_FRONT = $(COMPOSE_RUN) front
21 | COMPOSE_RUNCI_FRONT = $(COMPOSE_RUNCI) front
22 | COMPOSE_RUN_API = $(COMPOSE_RUN) api
23 |
24 | # production docker-compose
25 | COMPOSEPROD = docker-compose -f ./docker-compose.yml -f ./docker-compose.prod.yml
26 |
27 | # kubernetes
28 | KUBECTL_CONFIG = -f ./deployments/api.yml -f ./deployments/front.yml
29 |
30 | default: help
31 |
32 | .PHONY: build-front-assets dev-logs-api dev-logs-front dev-logs dev-ps dev-start-d dev-start dev-stop docker-build-prod docker-images-clean docker-images-id docker-images-name docker-images kube-ps kube-start-no-rebuild kube-start kube-stop prod-logs-api prod-logs-front prod-logs prod-ps prod-start-d-no-rebuild prod-start-d prod-start-no-rebuild prod-start prod-stop test-api test-front test
33 |
34 | # rename ?
35 | build-front-assets: ## Build frontend assets into ./front/build folder
36 | $(COMPOSE_RUN_FRONT) npm run build
37 |
38 | test-front: ## Test react frontend
39 | $(COMPOSE_RUNCI_FRONT) npm run -s test
40 |
41 | test-api: ## Test golang backend
42 | $(COMPOSE_RUN_API) go test -run ''
43 |
44 | test: test-front test-api ## 🌡 Test both frontend and backend
45 |
46 | docker-images: ## List project's docker images
47 | @docker images --filter=reference='$(DOCKER_IMAGE_PREFIX)*'
48 |
49 | docker-images-name: ## List project's docker images formatted as :
50 | @docker images --format "{{.Repository}}:{{.Tag}}" --filter=reference='$(DOCKER_IMAGE_PREFIX)*'
51 |
52 | docker-images-id: ## List project's docker images formatted as
53 | @docker images --quiet --filter=reference='$(DOCKER_IMAGE_PREFIX)*'
54 |
55 | docker-images-clean: ## Clean dangling images (tagged as )
56 | docker rmi $(shell docker images -q --filter="dangling=true")
57 |
58 | docker-build-prod: ## Build production images
59 | $(MAKE) build-front-assets
60 | docker build ./api -t $(DOCKER_IMAGE_NAME_API_PROD):$(TAG)
61 | docker build . -f Dockerfile.prod -t $(DOCKER_IMAGE_NAME_NGINX):$(TAG)
62 |
63 | dev-start: ## 🐳 Start development stack
64 | $(COMPOSE) up
65 | dev-start-d: ## Start development stack (in daemon mode)
66 | $(COMPOSE) up -d
67 | dev-stop: ## Stop development stack
68 | $(COMPOSE) down
69 | dev-ps: ## List development stack active containers
70 | $(COMPOSE) ps
71 | dev-logs: ## 🐳 Follow ALL logs (dev)
72 | $(COMPOSE) logs -f
73 | dev-logs-front: ## Follow front logs (dev)
74 | $(COMPOSE) logs -f front
75 | dev-logs-api: ## Follow api logs (dev)
76 | $(COMPOSE) logs -f api
77 |
78 | prod-start: ## 🐳 Start production stack (bundles frontend before)
79 | $(MAKE) build-front-assets
80 | $(COMPOSEPROD) up --build
81 | prod-start-d: ## Start production stack (in daemon mode)
82 | $(MAKE)build-front-assets
83 | $(COMPOSEPROD) up --build -d
84 | prod-start-no-rebuild: ## 🐳 Start production stack without recreating docker images
85 | $(COMPOSEPROD) up
86 | prod-start-d-no-rebuild: ## Start production stack (in daemon mode) without recreating docker images
87 | $(COMPOSEPROD) up -d
88 | prod-stop: ## Stop production stack
89 | $(COMPOSEPROD) down
90 | prod-ps: ## List production stack active containers
91 | $(COMPOSEPROD) ps
92 | prod-logs: ## 🐳 Follow ALL logs (prod)
93 | $(COMPOSEPROD) logs -f
94 | prod-logs-front: ## Follow front logs (prod)
95 | $(COMPOSEPROD) logs -f front
96 | prod-logs-api: ## Follow api logs (prod)
97 | $(COMPOSEPROD) logs -f api
98 |
99 | kube-start-no-rebuild: ## Create kubernetes deployment without recreating docker images
100 | kubectl create $(KUBECTL_CONFIG)
101 | kube-start: ## ☸️ Create kubernetes deployment with fresh docker images
102 | $(MAKE) docker-build-prod
103 | @echo ""
104 | $(MAKE) kube-start-no-rebuild
105 | @echo "\nYou may use $(OK_COLOR)make kube-start-no-rebuild$(NO_COLOR) next time to avoid rebuilding images each time\n"
106 | kube-stop: ## Delete kubernetes deployment with fresh docker images
107 | kubectl delete $(KUBECTL_CONFIG)
108 | kube-ps: ## List kubernetes pods and services
109 | kubectl get pods,services
110 |
111 | help:
112 | @grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
113 |
114 | list-phony:
115 | # List all the tasks to add the to .PHONY (choose between inlined and linefeed)
116 | # bash variables are expanded with $$
117 | # make|sed 's/\|/ /'|awk '{printf "%s+ ", $1}'
118 | # make|sed 's/\|/ /'|awk '{print $1}'
119 | @$(MAKE) help|sed 's/\|/ /'|awk '{printf "%s ", $$1}'
120 | @echo "\n"
121 | @$(MAKE) help|sed 's/\|/ /'|awk '{print $$1}'
122 |
123 | # deprecated - example of how to call a function from an other .sh file
124 | image-exists:
125 | @. $(UTILS); image_exists $(DOCKER_IMAGE_NAME_NGINX):$(TAG)
--------------------------------------------------------------------------------
/front/src/assets/images/docker-logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
71 |
--------------------------------------------------------------------------------
/front/src/assets/images/kubernetes-logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
85 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # docker-experiments
2 |
3 | [](https://circleci.com/gh/topheman/docker-experiments)
4 |
5 |
6 |
7 | This started as a simple use case to discover `docker` and `docker-compose` 🐳 :
8 |
9 | * A [front](front) made with create-react-app, running in a nodejs container for development
10 | * A very simple [api](api) made in go (the challenge is also not to have everything in JavaScript)
11 |
12 | I also setup **deployments on a local kubernetes** ☸️ and tests are running on [CircleCI](https://circleci.com/gh/topheman/docker-experiments) on each push.
13 |
14 | ## TL;DR
15 |
16 | You are a true developer? You don't RTFM? After all, this is why we have docker ... not to bother with all the boring setup/install steps ... 😉
17 |
18 | ```shell
19 | git clone https://github.com/topheman/docker-experiments.git
20 | cd docker-experiments
21 | docker-compose up -d
22 | ```
23 |
24 | You are good to go with a development server running at [http://localhost:3000](http://localhost:3000), with the front in react, the api in go and everything hot reloading. 👏
25 |
26 | Try to take a few minutes to read the doc bellow ... 😇
27 |
28 | ## Summary
29 |
30 | * [Prerequisites](#prerequisites)
31 | * [Setup](#setup)
32 | * [Development 🛠](#development)
33 | * [Tests 🌡](#tests)
34 | * [Production - docker-compose 🐳](#production---docker-compose)
35 | * [Deployment - kubernetes ☸️](#deployment---kubernetes)
36 | * [Notes 📋](#notes)
37 | * [Docker Multi-stage builds](#docker-multi-stage-builds)
38 | * [Docker networks / Kubernetes services](#docker-networks--kubernetes-services)
39 | * [Restart on failure](#restart-on-failure)
40 | * [Commands](#commands)
41 | * [Docker commands](#docker-commands)
42 | * [Kubernetes commands](#kubernetes-commands)
43 | * [FAQ](#faq)
44 | * [CircleCI](#circleci)
45 | * [How to use latest version of docker-compose / docker-engine](#how-to-use-latest-version-of-docker-compose--docker-engine)
46 | * [Docker vs Machine executors](#docker-vs-machine-executors)
47 | * [Why does /api fallbacks to index.html in production](#why-does-api-fallbacks-to-indexhtml-in-production)
48 | * [What's next?](#whats-next)
49 | * [Resources](#resources)
50 | * [Author](#author)
51 |
52 | ## Prerequisites
53 |
54 | You need to have installed:
55 |
56 | * docker / docker-compose
57 | * npm / node (optional)
58 | * local kubernetes server and client (only if you want to play with kubernetes deployment - more about that on the [deployment section](#deployment---kubernetes))
59 |
60 | ## Setup
61 |
62 | ```shell
63 | git clone https://github.com/topheman/docker-experiments.git
64 | ```
65 |
66 | A [Makefile](Makefile) is available that automates all the commands that are described bellow. For each section, you'll find the related commands next to the 🖊 emoji.
67 |
68 | Just run `make help` to see the whole list.
69 |
70 | ## Development
71 |
72 | ### Launch development
73 |
74 | ```shell
75 | docker-compose up -d
76 | ```
77 |
78 | This will create (if not already done) and launch a whole development stack, based on [docker-compose.yml](docker-compose.yml), [docker-compose.override.yml](docker-compose.override.yml), [api/Dockerfile](api/Dockerfile) and [front/Dockerfile](front/Dockerfile) - following images:
79 |
80 | * `topheman/docker-experiments_front_development`: for react development (based on nodejs image)
81 | * `topheman/docker-experiments_api_development`: for golang in development mode (using [fresh](https://github.com/pilu/fresh) to build and restart the go webserver when you change the sources)
82 | * The `services.api.command` entry in [docker-compose.override.yml](docker-compose.override.yml) will override the default `RUN` command and start a dev server (instead of running the binary compiled in the container at build time)
83 |
84 | Go to http://localhost:3000/ to access the frontend, you're good to go, the api is accessible at http://localhost:5000/.
85 |
86 | 🖊 `make dev-start`, `make dev-start-d`, `make dev-stop`, `make dev-ps`, `make dev-logs`, `make dev-logs-front`, `make dev-logs-api`
87 |
88 | ## Tests
89 |
90 | ### Launch tests
91 |
92 | ```shell
93 | docker-compose run --rm -e CI=true front npm run -s test && docker-compose run --rm api go test -run ''
94 | ```
95 |
96 | 🖊 `make test`, `make test-front`, `make test-api`
97 |
98 | ## Production - docker-compose
99 |
100 | This section is about **testing the production images with docker-compose** 🐳 (check the [deployment section](#deployment---kubernetes) to deploy with kubernetes locally).
101 |
102 | Make sure you have built the frontend with `docker-compose run --rm front npm run build`, then:
103 |
104 | ```shell
105 | docker-compose -f ./docker-compose.yml -f ./docker-compose.prod.yml up --build
106 | ```
107 |
108 | Note: make sure to use the `--build` flag so that it will rebuild the images if anything changed (in the source code or whatever), thanks to [docker images layers](https://docs.docker.com/develop/develop-images/dockerfile_best-practices/), only changes will be rebuilt, based on cache (not the whole image).
109 |
110 | This will create (if not already done) and launch a whole production stack:
111 |
112 | * No nodejs image (it should not be shipped to production, the development image is only used to launch the container that creates the build artefacts with create-react-app).
113 | * `topheman/docker-experiments_api_production`: for the golang server (with the app compiled) - containing only the binary of the golang app (that way the image)
114 | * `topheman/docker-experiments_nginx`: which will:
115 | * serve the frontend (copied from `/front/build`)
116 | * proxy `/api` requests to `http://api:5000` (the docker subnet exposed by the golang api container)
117 |
118 | Access [http://localhost](http://localhost) and you're good to go.
119 |
120 | 🖊 `make prod-start`, `make prod-start-d`, `make prod-start-no-rebuild`, `make prod-start-d-no-rebuild`, `make prod-stop`, `make prod-ps`, `make prod-logs`, `make prod-logs-front`, `make prod-logs-api`
121 |
122 | ## Deployment - kubernetes
123 |
124 | This section is about **deploying the app locally with kubernetes** ☸️ (not tested with a cloud provider). To stay simple, there aren't any TLS termination management (only port 80 exposed).
125 |
126 | Local kubernetes server and client:
127 |
128 | * If you have the latest docker for Mac, both are [shipping with kubernetes](https://blog.docker.com/2018/01/docker-mac-kubernetes/)
129 | * Otherwise, you can use [minikube](https://github.com/kubernetes/minikube) for the server and install [`kubectl`](https://kubernetes.io/docs/tasks/tools/install-kubectl/) (kubernetes client)
130 |
131 | The files descripting the deployments are stored in the [deployments](deployments) folder. You will find two files, each containing the deployment and the service.
132 |
133 | ### Deploy with kubernetes
134 |
135 | 1) If you haven't built the frontend, run `docker-compose run --rm front npm run build`
136 |
137 | 2) Build the production images:
138 |
139 | ```shell
140 | docker build ./api -t topheman/docker-experiments_api_production:1.0.1
141 | docker build . -f Dockerfile.prod -t topheman/docker-experiments_nginx:1.0.1
142 | ```
143 |
144 | Note: They are tagged `1.0.1`, same version number as in the deployments files (want to put an other version number ? Don't forget to update the deployment files). For the moment, I'm not using [Helm](http://helm.readthedocs.io/en/latest/generate-and-template/) that let's you do string interpolation on yml files.
145 |
146 | 3) Create your pods and services
147 |
148 | Make sure nothing is up on port `80`, then:
149 |
150 | ```shell
151 | kubectl create -f ./deployments/api.yml -f ./deployments/front.yml
152 | ```
153 |
154 | You're good to go, check out [http://localhost](http://localhost)
155 |
156 | To stop and delete the pods/services you created:
157 |
158 | ```shell
159 | kubectl delete -f ./deployments/api.yml -f ./deployments/front.yml
160 | ```
161 |
162 | They won't stop right away, you can list them and see their status with:
163 |
164 | ```shell
165 | kubectl get pods,services
166 | ```
167 |
168 | [More commands](#kubernetes-commands)
169 |
170 | 🖊 `make kube-start`, `make kube-start-no-rebuild`, `make kube-stop`, `make kube-ps`
171 |
172 | ## Notes
173 |
174 | ### Docker Multi-stage builds
175 |
176 | Thanks to [docker multi-stage builds](https://docs.docker.com/develop/develop-images/multistage-build/), the golang application is built in a docker golang:alpine image (which contains all the tooling for golang such as compiler/libs ...) and produces a small image with only a binary in an alpine image (small Linux distrib).
177 |
178 | The targets for multi-stage build are specified in the `docker*.yml` config files.
179 |
180 | The [api/Dockerfile](api/Dockerfile) will create such a production image by default.
181 |
182 | You can tell the difference of weight:
183 |
184 | ```
185 | docker images
186 | topheman/docker-experiments_api_production latest 01f1b575fae6 About a minute ago 11.5MB
187 | topheman/docker-experiments_api_development latest fff1ef3ec29e 8 minutes ago 426MB
188 | topheman/docker-experiments_front_development latest 4ed3aea602ef 22 hours ago 225MB
189 | ```
190 |
191 | ### Docker networks / Kubernetes services
192 |
193 | In development, the api server in golang is available at [http://localhost:5000](http://localhost:5000) and proxied onto [http://localhost:3000/api](http://localhost:3000/api) (the same port as the front, thanks to create-react-app [proxy](front/README.md#proxy)).
194 |
195 | In **production** mode, we only want the golang server to be available via `/api` (we don't want to expose it on it's own port).
196 |
197 | To make it work:
198 |
199 | * the docker-compose golang api service is named `api` - [see docker-compose.yml](docker-compose.yml).
200 | * the kubernetes services exposing the api is also named `api` - [see deployments/api.yml](deployments/api.yml)
201 |
202 | That way, the nginx conf can work with both docker-compose AND kubernetes, proxying `http://api` - [see nginx/site.conf](nginx/site.conf).
203 |
204 | ### Restart on failure
205 |
206 | If your app exits with a failure code (greater than 0) inside the container, you'll want it to restart (like you would do with pm2 and node apps).
207 |
208 | With [docker-compose/production](#production---docker-compose), the, directive `restart: on-failure` in the [docker-compose.yml](docker-compose.yml) file will ensure that. You'll be able to check it by clicking on the "exit 1 the api server" button, which will exit the golang api. You'll see that the uptime is back counting from 0 seconds.
209 |
210 | With [kubernetes/deployment](#deployment---kubernetes), I setup 2 replicas of the api server, so when you retrieve the infos, the hostname might change according of the api pod you're balance on.
211 |
212 | Exiting one pod won't break the app, it will fallback on the remaining replica. If you exit the two pods, you'll get an error retrieving infos, until one of the pod is back up by kubernetes (check their status with `kubectl get pods`).
213 |
214 | ### Commands
215 |
216 | #### Docker commands
217 |
218 | * `docker-compose run --rm front npm run test`: launch a front container in *development* mode and run tests
219 | * `docker-compose -f ./docker-compose.yml run --rm api `: launch an api container in *production* mode and run ``
220 | * `docker-compose down`: stop and remove containers, networks, volumes, and images created by `docker-compose up`
221 |
222 | Don't want to use `docker-compose` (everything bellow is already specified in the `docker*.yml` files - only dropping to remember the syntax for the futur) ?
223 |
224 | * `docker build ./api -t topheman/docker-experiments_api_production:1.0.1`: build the `api` and tag it as `topheman/docker-experiments_api_production:1.0.1` based on [api/Dockerfile](api/Dockerfile)
225 | * `docker run -d -p 5000:5000 topheman/docker-experiments_api_production:1.0.1`: runs the `topheman/docker-experiments_api_production:1.0.1` image previously created in daemon mode and exposes the ports
226 | * `docker build ./front -t topheman/docker-experiments_front_development:1.0.1`: build the `front` and tag it as `topheman/docker-experiments_front_development:1.0.1` based on [front/Dockerfile](front/Dockerfile)
227 | * `docker run --rm -p 3000:3000 -v $(pwd)/front:/usr/front -v front-deps:/usr/front/node_modules topheman/docker-experiments_front_development:1.0.1`:
228 | * runs the `topheman/docker-experiments_front_development:1.0.1` image previously created in attach mode
229 | * exposes the port 3000
230 | * creates (if not exists) and bind the volumes
231 | * the container will be removed once you kill the process (`--rm`)
232 | * `docker rmi $(docker images -q --filter="dangling=true")`: remove dangling images (layers that have no more relationships to any tagged image. Tagged as , they no longer serve a purpose and consume disk space)
233 |
234 | #### Kubernetes commands
235 |
236 | [kubectl Cheat Sheet](https://kubernetes.io/docs/reference/kubectl/cheatsheet/)
237 |
238 | * `kubectl create -f ./deployments/api.yml -f ./deployments/front.yml`: creates the resources specified in the declaration files
239 | * `kubectl delete -f ./deployments/api.yml -f ./deployments/front.yml`: deletes resources specified in the declaration files
240 | * `kubectl scale --replicas=3 deployment/docker-experiments-api-deployment`: scales up the api through 3 pods
241 |
242 | ## FAQ
243 |
244 | ### CircleCI
245 |
246 | #### How to use latest version of docker-compose / docker-engine
247 |
248 | I had the following error on my [first build](https://circleci.com/gh/topheman/docker-experiments/2):
249 |
250 | > ERROR: Version in "./docker-compose.yml" is unsupported. You might be seeing this error because you're using the wrong Compose file version. Either specify a supported version ("2.0", "2.1", "3.0", "3.1", "3.2") and place your service definitions under the `services` key, or omit the `version` key and place your service definitions at the root of the file to use version 1.
251 | >
252 | > For more on the Compose file format versions, see https://docs.docker.com/compose/compose-file/
253 |
254 | The reason was because I'm using **docker-compose file format v3.4**, which doesn't seem to be supported by the version of docker-engine used on the default setup of CircleCI - [see compatibility matrix](https://docs.docker.com/compose/compose-file/#compose-and-docker-compatibility-matrix).
255 |
256 | With CircleCI, in **machine executor mode**, you can change/customize the image your VM will be running (by default: `circleci/classic:latest`) - see the [list of images available](https://circleci.com/docs/2.0/configuration-reference/#machine). I simply changed the image to use:
257 |
258 | ```diff
259 | version: 2
260 | jobs:
261 | build:
262 | - machine: true
263 | + machine:
264 | + image: circleci/classic:201808-01
265 | ```
266 |
267 | Checkout [.circleci/config.yml](.circleci/config.yml)
268 |
269 | Note: Why use docker-compose file format v3.4 ? To take advantage of the `target` attribute.
270 |
271 | #### Docker vs Machine executors
272 |
273 | > You can not build Docker within Docker.
274 |
275 | To build/push docker images, you have two solutions on CircleCI:
276 |
277 | * Use the [machine executor mode](https://circleci.com/docs/2.0/executor-types/#using-machine): your jobs will be run in a dedicated, ephemeral Virtual Machine (VM) - so, you can directly run docker inside
278 | * Use the [setup_remote_docker](https://circleci.com/docs/2.0/building-docker-images/#overview) key: a remote environment will be created, and your current primary container will be configured to use it. Then, any docker-related commands you use will be safely executed in this new environment
279 |
280 | ### Why does /api fallbacks to index.html in production
281 |
282 | #### Service Worker
283 |
284 | create-react-app ships with a service worker by default which implementation is based on [sw-precache-webpack-plugin](https://github.com/goldhand/sw-precache-webpack-plugin) (a Webpack plugin that generates a service worker using [sw-precache](https://github.com/GoogleChromeLabs/sw-precache) that will cache webpack's bundles' emitted assets).
285 |
286 | It means that a `service-worker.js` file will be created at build time, listing your public static assets that the service worker will cache using a [cache first](https://developers.google.com/web/fundamentals/instant-and-offline/offline-cookbook/#cache-falling-back-to-network) strategy (on a request for an asset, will first hit the service worker cache and serve it, then call the network and update the cache - this makes the app fast and offline-first).
287 |
288 | From the [create-react-app doc](front/README.cra.md#serving-apps-with-client-side-routing):
289 |
290 | > On a production build, and in a browser that supports [service workers](https://developers.google.com/web/fundamentals/getting-started/primers/service-workers),
291 | the service worker will automatically handle all navigation requests, like for
292 | `/todos/42` or `/api`, by serving the cached copy of your `index.html`. This
293 | service worker navigation routing can be configured or disabled by
294 | [`ejecting`](front/README.cra.md#npm-run-eject) and then modifying the
295 | [`navigateFallback`](https://github.com/GoogleChrome/sw-precache#navigatefallback-string)
296 | and [`navigateFallbackWhitelist`](https://github.com/GoogleChrome/sw-precache#navigatefallbackwhitelist-arrayregexp)
297 | options of the `SWPreachePlugin` [configuration](https://github.com/facebook/create-react-app/blob/master/packages/react-scripts/config/webpack.config.prod.js).
298 |
299 | ## What's next?
300 |
301 | The next thing that will be comming are:
302 |
303 | * [x] setup CI
304 | * [x] using [nginx](https://www.nginx.com/) as a reverse-proxy to:
305 | * [x] serve the golang api which is in its own container on `/api`
306 | * [x] make a build of the front and serve it at the root
307 | * [x] use [kubernetes](https://kubernetes.io/) to automate deployment
308 | * [x] start by playing in local with [minikube](https://github.com/kubernetes/minikube)
309 | * [ ] add [Helm](http://helm.readthedocs.io/en/latest/generate-and-template/) to do string interpolation in yaml description files ?
310 | * [ ] linting / formatting + pre-commit hooks
311 | * [ ] add linting / formatting support (eslint/prettier) with advanced config for the JavaScript part
312 | * [ ] back it up with pre-commit hooks (husky ?)
313 | * The challenge being:
314 | * any npm dependency is currently installed on a volume mounted inside the front container (not accessible by host)
315 | * how to elegantly have lint/formatting task also running on host (for vscode plugins for example but also npm tasks like), without relying on global modules (this would be cheating 😉 + we should not assume anything about the computer our project is cloned on)
316 | * how to elegantly share pre-commit hooks ? (using husky would mean an `npm install` at the root of the project)
317 |
318 | *This is still in progress*.
319 |
320 | ## Resources
321 |
322 | * Docker
323 | * 📺 💯 [Better understand containers by coding one from scratch by Liz Rice](https://twitter.com/topheman/status/1014936620309647361)
324 | * 📺 [Create a Development Environment with Docker Compose by Mark Ranallo](https://www.youtube.com/watch?v=Soh2k8lCXCA)
325 | * 📺 [Rapid Development With Docker Compose](https://www.youtube.com/watch?v=o6SScget37w)
326 | * [Golang and Docker for development and production](https://medium.com/statuscode/golang-docker-for-development-and-production-ce3ad4e69673) - use [pilu/fresh](https://github.com/pilu/fresh) to rebuild on changes in development
327 | * [Create the smallest and secured golang docker image based on scratch](https://medium.com/@chemidy/create-the-smallest-and-secured-golang-docker-image-based-on-scratch-4752223b7324)
328 | * [docker-compose with multi-stage build target (official doc)](https://docs.docker.com/compose/compose-file/#target)
329 | * Kubernetes
330 | * 📺 [Learn Kubernetes by CoderJourney](https://www.youtube.com/playlist?list=PLbG4OyfwIxjFE5Ban_n2JdGad4EDWmisR)
331 | * [Source code](https://github.com/coderjourney/meal_plan)
332 | * 📺 [Run Kubernetes Locally Using Minikube by CoderJourney](https://coderjourney.com/run-kubernetes-locally-using-minikube/)
333 | * [Setup bash completion for `kubectl`](https://twitter.com/topheman/status/1022939077602156546)
334 |
335 | More bookmarks from my research:
336 |
337 | * Docker
338 | * [Generic Docker Makefile](https://github.com/mvanholsteijn/docker-makefile)
339 | * [Awesome-docker - A curated list of Docker resources and projects](https://awesome-docker.netlify.com/)
340 | * Kubernetes
341 | * [Kubernetes & Traefik 101— When Simplicity Matters](https://dev.to/geraldcroes/kubernetes--traefik-101-when-simplicity-matters-6k6)
342 | * [Tutorial : Getting Started with Kubernetes with Docker on Mac](https://rominirani.com/tutorial-getting-started-with-kubernetes-with-docker-on-mac-7f58467203fd)
343 | * [kubernetes/dashboard](https://github.com/kubernetes/dashboard)
344 | * [Kubernetes Ingress](https://medium.com/@cashisclay/kubernetes-ingress-82aa960f658e)
345 | * [Setting up Nginx Ingress on Kubernetes](https://hackernoon.com/setting-up-nginx-ingress-on-kubernetes-2b733d8d2f45)
346 | * [Advanced kubernetes ingress](https://koudingspawn.de/advanced-ingress/)
347 | * [Kubernetes NodePort vs LoadBalancer vs Ingress? When should I use what?](https://medium.com/google-cloud/kubernetes-nodeport-vs-loadbalancer-vs-ingress-when-should-i-use-what-922f010849e0)
348 | * [Kubectl apply vs kubectl create?](https://stackoverflow.com/questions/47369351/kubectl-apply-vs-kubectl-create)
349 | * [awesome-kubernetes - A curated list for awesome kubernetes sources](https://ramitsurana.github.io/awesome-kubernetes/)
350 | * [Tutoriel Linux : Makefile](https://youtu.be/2VV9FAQWHdw)
351 |
352 | ## Author
353 |
354 | [Christophe Rosset](https://github.com/topheman)
--------------------------------------------------------------------------------
/front/src/assets/images/gopher.svg:
--------------------------------------------------------------------------------
1 |
46 |
--------------------------------------------------------------------------------