This started as a simple use case to discover
- A front made with create-react-app, running in a nodejs container for development
I also setup deployments on a local kubernetes
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 ...
git clone https://github.com/topheman/docker-experiments.git cd docker-experiments docker-compose up -d
You are good to go with a development server running at http://localhost:3000, with the front in react, the api in go and everything hot reloading.
Try to take a few minutes to read the doc bellow ...
- Production - docker-compose
- Deployment - kubernetes
- What's next?
You need to have installed:
- docker / docker-compose
- npm / node (optional)
- local kubernetes server and client (only if you want to play with kubernetes deployment - more about that on the deployment section)
git clone https://github.com/topheman/docker-experiments.git
A Makefile is available that automates all the commands that are described bellow. For each section, you'll find the related commands next to the
make help to see the whole list.
docker-compose up -d
topheman/docker-experiments_front_development: for react development (based on nodejs image)
topheman/docker-experiments_api_development: for golang in development mode (using fresh to build and restart the go webserver when you change the sources)
services.api.commandentry in docker-compose.override.yml will override the default
RUNcommand and start a dev server (instead of running the binary compiled in the container at build time)
docker-compose run --rm -e CI=true front npm run -s test && docker-compose run --rm api go test -run ''
Production - docker-compose
This section is about testing the production images with docker-compose
Make sure you have built the frontend with
docker-compose run --rm front npm run build, then:
docker-compose -f ./docker-compose.yml -f ./docker-compose.prod.yml up --build
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, only changes will be rebuilt, based on cache (not the whole image).
This will create (if not already done) and launch a whole production stack:
- 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).
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)
topheman/docker-experiments_nginx: which will:
- serve the frontend (copied from
http://api:5000(the docker subnet exposed by the golang api container)
- serve the frontend (copied from
Access http://localhost and you're good to go.
Deployment - kubernetes
This section is about deploying the app locally with kubernetes
Local kubernetes server and client:
- If you have the latest docker for Mac, both are shipping with kubernetes
- Otherwise, you can use minikube for the server and install
The files descripting the deployments are stored in the deployments folder. You will find two files, each containing the deployment and the service.
Deploy with kubernetes
If you haven't built the frontend, run
docker-compose run --rm front npm run build
Build the production images:
docker build ./api -t topheman/docker-experiments_api_production:1.0.1 docker build . -f Dockerfile.prod -t topheman/docker-experiments_nginx:1.0.1
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 that let's you do string interpolation on yml files.
- Create your pods and services
Make sure nothing is up on port
kubectl create -f ./deployments/api.yml -f ./deployments/front.yml
You're good to go, check out http://localhost
To stop and delete the pods/services you created:
kubectl delete -f ./deployments/api.yml -f ./deployments/front.yml
They won't stop right away, you can list them and see their status with:
kubectl get pods,services
Docker Multi-stage builds
Thanks to docker multi-stage builds, 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).
The targets for multi-stage build are specified in the
docker*.yml config files.
The api/Dockerfile will create such a production image by default.
You can tell the difference of weight:
docker images topheman/docker-experiments_api_production latest 01f1b575fae6 About a minute ago 11.5MB topheman/docker-experiments_api_development latest fff1ef3ec29e 8 minutes ago 426MB topheman/docker-experiments_front_development latest 4ed3aea602ef 22 hours ago 225MB
Docker networks / Kubernetes services
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).
To make it work:
- the docker-compose golang api service is named
api- see docker-compose.yml.
- the kubernetes services exposing the api is also named
api- see deployments/api.yml
That way, the nginx conf can work with both docker-compose AND kubernetes, proxying
http://api - see nginx/site.conf.
Restart on failure
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).
With docker-compose/production, the, directive
restart: on-failure in the 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.
With kubernetes/deployment, 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.
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).
docker-compose run --rm front npm run test: launch a front container in development mode and run tests
docker-compose -f ./docker-compose.yml run --rm api <cmd>: launch an api container in production mode and run
docker-compose down: stop and remove containers, networks, volumes, and images created by
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) ?
docker build ./api -t topheman/docker-experiments_api_production:1.0.1: build the
apiand tag it as
topheman/docker-experiments_api_production:1.0.1based on api/Dockerfile
docker run -d -p 5000:5000 topheman/docker-experiments_api_production:1.0.1: runs the
topheman/docker-experiments_api_production:1.0.1image previously created in daemon mode and exposes the ports
docker build ./front -t topheman/docker-experiments_front_development:1.0.1: build the
frontand tag it as
topheman/docker-experiments_front_development:1.0.1based on front/Dockerfile
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:
- runs the
topheman/docker-experiments_front_development:1.0.1image previously created in attach mode
- exposes the port 3000
- creates (if not exists) and bind the volumes
- the container will be removed once you kill the process (
- runs the
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)
kubectl create -f ./deployments/api.yml -f ./deployments/front.yml: creates the resources specified in the declaration files
kubectl delete -f ./deployments/api.yml -f ./deployments/front.yml: deletes resources specified in the declaration files
kubectl scale --replicas=3 deployment/docker-experiments-api-deployment: scales up the api through 3 pods
How to use latest version of docker-compose / docker-engine
I had the following error on my first build:
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
serviceskey, or omit the
versionkey and place your service definitions at the root of the file to use version 1.
For more on the Compose file format versions, see https://docs.docker.com/compose/compose-file/
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.
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. I simply changed the image to use:
version: 2 jobs: build: - machine: true + machine: + image: circleci/classic:201808-01
Note: Why use docker-compose file format v3.4 ? To take advantage of the
Docker vs Machine executors
You can not build Docker within Docker.
To build/push docker images, you have two solutions on CircleCI:
- Use the machine executor mode: your jobs will be run in a dedicated, ephemeral Virtual Machine (VM) - so, you can directly run docker inside
- Use the setup_remote_docker 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
Why does /api fallbacks to index.html in production
create-react-app ships with a service worker by default which implementation is based on sw-precache-webpack-plugin (a Webpack plugin that generates a service worker using sw-precache that will cache webpack's bundles' emitted assets).
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 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).
From the create-react-app doc:
On a production build, and in a browser that supports service workers, the service worker will automatically handle all navigation requests, like for
/api, by serving the cached copy of your
index.html. This service worker navigation routing can be configured or disabled by
ejectingand then modifying the
navigateFallbackWhitelistoptions of the
The next thing that will be comming are:
- setup CI
- using nginx as a reverse-proxy to:
- serve the golang api which is in its own container on
- make a build of the front and serve it at the root
- serve the golang api which is in its own container on
- use kubernetes to automate deployment
- linting / formatting + pre-commit hooks
- back it up with pre-commit hooks (husky ?)
- The challenge being:
- any npm dependency is currently installed on a volume mounted inside the front container (not accessible by host)
- 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)
- how to elegantly share pre-commit hooks ? (using husky would mean an
npm installat the root of the project)
This is still in progress.
📺 💯Better understand containers by coding one from scratch by Liz Rice 📺Create a Development Environment with Docker Compose by Mark Ranallo 📺Rapid Development With Docker Compose
- Golang and Docker for development and production - use pilu/fresh to rebuild on changes in development
- Create the smallest and secured golang docker image based on scratch
More bookmarks from my research:
- Kubernetes & Traefik 101— When Simplicity Matters
- Tutorial : Getting Started with Kubernetes with Docker on Mac
- Kubernetes Ingress
- Kubectl apply vs kubectl create?
- awesome-kubernetes - A curated list for awesome kubernetes sources
- Tutoriel Linux : Makefile