# Building a clean development environment with Docker Desktop (web application edition) ## Development Ideal and Real ### Different language versions You would need to install/switch to another version for different projects ### Dependency management (composer for PHP, npm for node.js) This can't be reused and install them freshly for each project ### Environment variables, PATH, config files You need to remember which config files to be used, which web server for this project etc ### Virtual Machine It enable developers to prepare different environments. #### Software to implement hardware virtualization * vmqEW * Hyper-v * Vagrant It can run on hypervisor (software that creates and runs virtual machines) as well as normal OS (Windows, Linux) It is not really ideal to run that on normal personal PC as it requires a lot of resouce (CPU, memory etc) ## What is Docker? The most famous product for containerization It prepare a container which contains minimul function to run applications. To run Linux server on Windows or Mac, prepare Linux VM as hypervisor then run the container on it. * Hypervisor.framework for Mac * WSL (Windows Subsystem for Linux) for Windows ### Comparison with VM * Fast movement * Easy copy/move * Dockerfile, Image * There is a way to share your images over internet * Fast start up * For OS point of view, container is like a notmal application. ![How is the different from virtual machines?](https://i.imgur.com/QzpWVEr.png) ## Docker Basic ![dockerfile workflow](https://i.imgur.com/btrWctd.png) ### Docker Image Docker Image is a template of the container. Like snapshot. images includes: * Linux distribution (Debian, apline) * Appliocation (Java, PHP) * Library, Framework (mySql, Laravel) * Program or settings file (PHP source code) * Process to start containers (start up web server) The same container can be reproduced as many times as possible as by using images. Therefore you can easily reproduce the same environment as other people or production environment. ### Dockerfile specification of a docker image. It enables reproductivity as you don't need to specify the options when you build an image dockerfile includes: * base image * command to execute on build (e.g. installing some library) * port number * file copy * volume mount * comamnd to execute on starting container You can find base image on DockerHub (OFFICIAL IMAGE) or GitHub. ### Image Registry (Registry) Place to share images. DockerHub, Amazon ECR, or you can build your own registry. :::success :bulb: Choose OFFICIAL IMAGE or VERIFIED PUBLISHER ::: ### Tag ::: warning <version\>-<function\>-<distribution\> ::: ##### version The vesrion of the application. e.g. Java version ##### function What kind of function comes with this container e.f. fpm, apache etc ##### distribution Base Linux distributor. e.g. apline, bustor etc * **bustor**: Typical Debian packet. It includes typical package. If the tag doesn't contain distributor, it normaly comes with bustor * **apline**: Light weight and famous on Docker user. As it comes with minimal function, you would need to install libraries as necessary (e.g. bash) ### Container Substantial the image. It has status of executed/stopped. ## Docker image command The commands are updated since docker version 1.13. You can still use the old command but better use the new command. | command | old command | explanation | |:-------------------- |:-------------- |:----------------------------- | | docker image ls | docker images | show all images | | docker image rm | docker rmi | delete image. | | docker image prune | - | delete all unnecessary images | | docker image pull | docker pull | get image from DockerHub | | docekr image push | docker push | registor image to DockerHub | | docekr image inspect | docker inspect | show details of the image | ### rm: delete image You can also specify <imageName: tag\> instead of imageID ```bash docekr image rm 28d825479be4 docker image rm php docker image php ``` If tag is not specified, latest is applied For force delete, use [-f] option ```bash docker image rm -f 28d825479be4 ``` ### prune unnecessary images means images which left like a trush by unexpected error. To delete all images which are not referred to the container, use -a option ```bash docker image prune -a ``` ## Docker containedr command | command | old command | explanation | | ------------------------ | -------------- |:---------------------------------------------- | | docker container ls | docker ps | list all containers | | docker container run | docker run | create and start container | | docker container logs | docker logs | get container logs | | docker container inspect | docker inspect | get info and details of the container | | docker container stats | docker stats | show resouce usage | | docker container stop | docker stop | stop container | | docker container create | docker create | create container | | docker container start | docker start | start contaienr | | docker restart | docker restart | restart container | | docker container attach | docker attach | connect to container | | docker container exec | docker exec | connect to container | | docker container rm | docker rm | delete container (container should be stopped) | | docker container prune | - | delete all the stopped containers | ### ls: list all containers show all started containers #### option: -a show all containers (both stopped and started) #### option: -q show all container IDs ### run: create and start container Use the downloaded image if exist, pull it from DockerHub if not exist. (combination of docker container create and docker container start) #### Frequently used options: | option | explanation | |:------ |:------------------------------- | | -name | name the container | | -it | execute interactive mode | | -d | execute it on background mode | | -p | specify the port | | -mount | folder mount | | -rm | delete container after finished | examples ```bash docker container run -it --rm php:7.4-apache /bin/bash ``` It create a container from php:7.4-apache image, execute shell of /bin/bash on startup. The shell is executed on root user. After exit from the container, it will be deleted automatically. ### attach/exec connect to container docker exec executes a new command / create a new process in the container’s environment docker attach just connects the standard input/output/error of the main process(with PID 1) inside the container to corresponding standard input/output/error of current terminal(the terminal you are using to run the command). When exit from the shell, it stops container with attach command while it doesn't stop the container with exec command To do something, use -it action (better use it always) ## Folder mount mount can remain folder/files on the actual OS so that the data leaves even though containers are deleted ### volume (<- recommended) Create a named volume by host and apply to the container. No need to think about where the volume is created (should not be controlled by host). faster access than bind | Command | explanation | |:-------------------- |:--------------------- | | docker volume create | create volume | | docker volume ls | list volumes | | docker volume rm | delete volume | | docker volume prune | delete unused volumes | Following creates volume named voltest at /tmp/voltest directory in the container. src=<volume name/> dst=<directory/> Volume gets created when not exists ```bash docker container run -it --rm --mount src=voltest,dst=/tmp/voltest php:7.4-apache /bin/bash ``` ### bind Apply mount specified by the host. It's used when host wants to control the content of the mount. Following the command create mount at physical folder ($(pwd)/test) when anything created under /tmp/test in the container ```bash mkdir test docker container run -it --rm --mount type=bind,src=$(pwd)/test,dst=/tmp/test php:7.4-apache /bin/bash ``` ### tmpfs Use PC memory as a temporary mount. When the container stops, the tmpfs mount is removed and files written there won’t be persisted. Cannot share contents between containers ## Dockerfile A Dockerfile is a text document that contains all the commands a user could call on the command line to assemble an image. | keyword | explanation | |:---------- |:------------------------------------------------------------- | | FROM | specify base image | | LABEL | add metadata | | RUN | command on build | | EXPOSE | specify network ports on which container listens at runtime | | COPY | copy file/folder to container | | ADD | with copy. Used when copying from unfreze zip or external URL | | CMD | Only once executed command when container runs | | ENTRYPOINT | command on run | | ARG | temp variable | | ENV | environmental variable | | USER | switch user | | WORKDIR | specify working direcotry | ### ENV Variables which can be used in the container. To use it within Dokcerfile, write as **${TS}** ### ARG Variables which only can be used in Dockerfile. Refer as **%TZ** To specify the value, use ```--build-arg``` ```bash docker image build --build-arg wdir=/var/www/html . ``` Or specify default within the Dockerfile ```bash ARG wdir="/var/www/html" ``` ### EXPOSE specify network ports on which container listens at runtime. You can specify whether the port listens on TCP or UDP, and the default is TCP if the protocol is not specified. for example nginx uses port 80 for web server and can execute nginx like that ```bash docker run -d -p 8080:80 nginx ``` So you can specify in Dockerfile ```dockerfile EXPORT 80 ``` ### CMD Write command which can be executed on docker run If multiple CMDs are written, only the last CMD gets executed For example if you write CMD like ```dockerfile # separate command with comma CMD["nginx", "-g", "deamon off;"] ``` These command is equivalent of ```bash nginx -g deamon off ``` ### ENTRYPOINT vs CMD When you run docker like this: `docker run -i -t ubuntu bash` the entrypoint is the default `/bin/sh -c`, the image is ubuntu and the command is bash. The command is run via the entrypoint. i.e., the actual thing that gets executed is `/bin/sh -c bash`. This allowed Docker to implement RUN quickly by relying on the shell's parser. Later on, people asked to be able to customize this, so **`ENTRYPOINT`** and **`--entrypoint`** were introduced. Everything after the image name, ubuntu in the example above, is the command and is passed to the entrypoint. When using the CMD instruction, it is exactly as if you were executing `docker run -i -t ubuntu <cmd>` The parameter of the entrypoint is `<cmd>` You will also get the same result if you instead type this command `docker run -i -t ubuntu`: a bash shell will start in the container because in the ubuntu Dockerfile a default CMD is specified: `CMD ["bash"]`. As everything is passed to the entrypoint, you can have a very nice behavior from your images. An example would be to have any cli as entrypoint. For instance, if you have a redis image, instead of running `docker run redisimg redis -H something -u toto get key`, you can simply have `ENTRYPOINT ["redis", "-H", "something", "-u", "toto"]` and then run like this for the same result: `docker run redisimg get key`. ### USER Default user is root. USER command switches user. * Linux normally specify execution user. * If a file gets created by root user, it becomes troublesome when normal user works on this file. To avoid that normal user creates a file on Dockerfile would be beneficial. ## Docker Compose Tool to build/start/stop container by using docker-compose.yml file instead of command to make sure the quality of the container is always the same. ### docker compose command | Command | Explanation | |:--------------------- |:--------------------------------- | | docker-compose up | service initiate/re-initiate | | docker-compose exec | execute command | | docker-compose logs | show logs | | docker-compose ps | list containers | | docker-compose stop | service stop | | docker-compose start | service start | | docker-compose down | service stop and delete container | | docker-compose build | image build | | docker-compose create | create contaienr | | docker-compose run | start service container | ### docker-compose.yml | Key | Explanation | |:-------------- |:-------------------------------------------------------- | | image: | base image | | container_name | container name | | expose: | Expose ports without publishing them to the host machine | | ports: | Expose ports which are exposed to the host machine | | environment: | environmental variable | | build: | Dockerfile path | | working_dir: | working directory | | volumes: | volume mount/ named volume | | depends_on: | service dependency | | stdin_open: | allocated stdin | | tty: | service container to run with a TTY | | command: | overwrite default command | #### docker-compose example ```dockerfile version: '3.8' services: db: image: postgres:13.1-apline container_name: 'ex11_db' expose: - "5432" environment: - POSTGRES_DB=postgres - POSTGRES_USER=postgres - POSTGRES_PASSWORD=postgres - HOGE=hoge web: image: ex11/web:1.0 build: ./web container_name: 'ex11_web' ports: - 8080:80 working_dir: '/var/www/html' volumes: - ./web/php.ini:/usr/local/etc/php/php/ini - type: bind source: ./web/src target: /var/www/html depends_on: - db ``` ### version docker-compose.yml file format version. You can use different command by versions ### services specify service (db and web in that saimple). It can be used to specify dependeicy or to refer service name on docker-compose command. You can have the different name as container_name but it's better to use the same one. ### image base image. ### container_name name of the container. You can access to the container by using that name. If the container name not specified, when it's docker-compose up: `folderName_serviceName_sequenceNumber` *i.e. ex11web1* If it's docker-compose run: `folderName_serviceName_run_identificationValue` *i.e. ex11webrun164e66b2b48e* ### expose Expose ports without publishing them to the host machine. On the following example specifying the default PstgreSQL database port: 5432. This specified port cannot be accessed from host, only from the other services. ### build specify where Dockerfile stays. If you specify build, the value at `image` will become an image name. ### port container port to host port. `host_port:container_port` same as `docker run -p option`. If you specify 8080:80, then the apache container can be aceessed on port 8080 ### working_dir Working directry when starting up the container ### volumes volume mount. Same as `docker run --mount option`. You can specify the relative path from the current position. you can use **volume**, **bind**, **tmpfs**. Normally you can specify the setting file as volume like ```dockerfile - ./web/php.ini:/usr/local/etc/php/php/ini ``` ### long version and short version The following is long version (version 3.2+) ```dockerfile volumes: - type: bind source: ./web/src target: /var/www/html ``` this is equiavent of ```dockerfile volumes: - ./web/src:/var/www/html ``` If you want to use a named volume, then volumes needs to be described. e.g. ```dockerfile web: volumes: - type: volume source: mydata target: /data volumes: mydata: ``` see [official document](https://docs.docker.com/compose/compose-file/#volumes) for more details ### depends_on It specifies relations. For the above sample, DB has to be executed to run Web. ### default command If you start up the service with `docker-compose up`, the application should keep executing otherwise the container will finish immediately. For example if the default command is bash, you need to specify option. ```dockerfile stdin_open: true tty: true command: /bin/bash ``` #### stdin_open, tty equiavent of docker run -it option. No need to specify this option if executing `docker-compose run` command as it is executed with -it command automatically #### command overwrite default command. You can overwrite this command by specifying the command with docker-compose run. ### docker-compose down stop & delete services. If the container runs it stops then deletes. `--rmi all` to delete images. `--volumes` will delete ## Remote Development 1. install Remote Development extension on VSCode 2. open .devcontainer/devcontaier.json file - devcontainer.json contains editor's setting and Docker Compose/Dockerfile 3. .vscode/launch.json can be used for debugging. ## Enable SSL/TSL 1. install mkcert on Windows, install Chocoloatery then ```bash choco install mkcert mkcert -install ``` on Mac use brew ```bash brew install mkcert mkcert -install ``` 2. create SSL/TSL self certificate run ```bash mkcert localhost 127.0.0.1 ``` To create following files ```bash localhost+1-key.pem localhost+1.pem ``` You can (or you should) rename the file such as `localhost-key.pem` Then you can use it to set up as ssl_certificate e.g. for nginx project conf file ```dockerfile listen 443 ssl; ssl_certificate /etc/certs/localhost.pem ssl_certificate_key /etc/certs/localhost-key.pem ```