How to raise a project in PHP in Docker for Windows

    What is an article


    This article is a set of simple, clear instructions and tips for users of Docker for Windows. This article will help PHP developers quickly pick up a project. The problems and their solutions are described. This article is useful for those who do not have an infinite resource of time to delve deeply into the problems of docker under Windows. The author would be infinitely grateful if he had come across a similar article earlier and the author would have saved a lot of time and effort. The text may contain errors and inaccuracies.


    What is not an article


    This article is not a complete and comprehensive guide to Docker for Windows. The article does not describe anything new and previously unknown facts are not disclosed - you can find all this yourself in different sources. The article also does not answer the question - did the chicken cross the road.




    The first step is to add a new administrator to the system


    This is an important step, if you skip it or do not follow it, then perhaps the following instructions do not make sense to you at all. Run Windows + R, lusrmgr.msc, "Local Users and Groups" will open. Further users, the context menu, in it "New user ...". Add a new user (e.g. dockerhost) with the required password. Password is required! Add membership to the Administrators group. Do not add other groups.


    Next, in Docker settings, Shared drivers, select the drives you need and enter the data (username and password) of the new user. Save the settings. If you previously entered the username and password of the user you are working under, enter the data for the new user. If you want to fix problems in an unexpected place - use a work account and do not read the article further.




    What configuration to start with?


    On the Internet, there are all sorts of configs docker-compose.yml for PHP web server. Some of them do not start under Windows. I recommend looking at the docker-compose.yml generator . I got the config from the generator right away and therefore later on I edited the config from there. The final result was uploaded to github . The generator option is bad for a few things. A description of the problem and its solutions are given below.




    Setting up persistent database storage


    Problem: It is not possible to store database files on a local disk. This should be taken as an axiom under Windows and try to find an acceptable solution so that the data is stored outside the container. For Windows, this is named volume. Just a couple of lines solves this problem.


        postgres:
          volumes:
            - db:/var/lib/postgresql/data
    volumes:
      db:

    Named volume are located in the / var / lib / docker / volumes / folder of the MobiLinuxVM docker virtual machine. There is no direct access to these files, only through an intermediary container. I don’t know the decision how to make this folder available from under Windows. To manage named volumes, use the docker volume command. For example, delete unused volumes docker volume prune.


    You do not need to steam with the rights and users for files in the named volume - the docker does everything for you, for which many thanks to him. In some manuals on setting up a permanent database storage in the docker, dances with the assignment of rights are given. Under Windows, without a named volume, you won’t succeed. Well, the first time it may start, but when it starts again, it will bend. You can’t even imagine how relieved it was when the permanent storage with named volume started working.




    Adding SSH Keys


    We will also add SSh keys through named volume. The reason is that special rights are needed for private keys, and regular volumes under Windows do not give this multiplicity. I will quote the required piece from docker-compose.yml


    services:
        php:
          volumes:
            - ssh:/root/.ssh
    volumes:
      ssh:

    For this to work, you need to copy the keys in the named volume, change the rights to private keys, test the connection. All these actions can be issued as a single bat file.


    docker run -v first_ssh:/root/.ssh --name helper busybox true
    docker cp ./.ssh/. helper:/root/.ssh/
    docker rm helper
    docker-compose exec php ls /root/.ssh
    docker-compose exec php chmod 600 /root/.ssh/id_rsa
    docker-compose exec php ssh git@github.com

    In the script, of course, substitute your volume name instead of first_ssh. Let me remind you that the name named volume will be formed by the docker as COMPOSE_PROJECT NAME + + ssh in our case. Instead of ./.ssh, put the path to your keys (well, or temporarily copy the folder with the keys to where you run this script). If you added your key to github, then at the very end github will greet. The script is one-time and should be run immediately after the successful first start of your containers (docker-compose up -d). Repeated launches do not make any sense, except if you deleted named volume.




    Networking between containers


    To connect between the containers described in one docker-compose.yml file, nothing is needed. It is enough to specify the service name or container name as the host name. Port numbers do not need to be changed - the default ports work. This could be completed if it were not necessary to receive and send requests to containers from another docker-compose.yml. There are also no problems with this, just specify the name of the networks and containers on the network. I will quote the desired section docker-compose.yml


    services:
        php:
          networks:
              # this network
              - default
              # external network
              - second_default
          external_links:
            - ${EXTERNAL_NGINX}
    networks:
      default:
        driver: bridge
      second_default:
        external: true

    Note that the network name second_default is not placed in environment variables because the global networks section does not allow the use of variables in principle. Then it makes no sense to use variables in the php section, where possible. for example, put there the name of the container from the external network. If we set EXTERNAL_NGINX = second_nginx in the .env file, then in the PHP code it will be enough to use the host name second_nginx to make an http request. The port is still 80 and you do not need to register it specifically. In the github repository, I added scripts for checking the connection between containers from different docker-compose.yml. It is enough to execute docker-compose exec php php get.php to make sure that it is working.


    When you start containers for the first time, the docker may swear. ERROR: Network second_network declared as external, but could not be found. Please create the network manually using docker network create second_defaultand try again. Actually, that's right, use the docker prompt and manually create a network.




    Log settings


    I would like to have logs from all services in one folder, accessible locally. This is done easily with the help of ordinary volumes. The main thing is to enable logging in the service itself. For php, these are options in the php.ini file, in nginx - in its config, in postgres - too. With php and nginx, everything is simple - there are corresponding files in our config. For postgres, you have to use the command line options (there is still a path through the postgresql.conf file, but it will be a bit more complicated)


    services:
        postgres:
          command: postgres -c logging_collector=on -c log_destination=stderr -c log_directory=/logs -c client_min_messages=notice -c log_min_messages=warning -c log_min_error_statement=warning -c log_min_duration_statement=0 -c log_statement=all -c log_error_verbosity=default
          volumes:
            - ${LOGS_DIR}:/logs

    The full text of docker-compose.yml in the turnip and at the end of the article. I must say that I'm still not happy with the configuration of the logs. While satisfied as is. Those who want to fine tune the logging can use the documentation of the corresponding services.




    Variables in .env


    An excellent option to make docker-compose.yml an almost universal config is the .env file. Full versatility will not work due to the global networks section, where it is impossible to specify environment variables. Of the features of my file are variables for postgress, which do not need to be written in docker-compose.yml itself. In php, you can use the database connection


    'dsn' => sprintf('pgsql:host=%s;dbname=%s', getenv('POSTGRES_HOST'), getenv('POSTGRES_DB')),



    Container images


    If you noticed, then I used my own images for php and nginx. This saved me time during testing. Nothing prevents you from using other container images - my config is just a demo. It’s easy to build your own images - in the turnip, look at the build folder, where used images are created.




    Conclusion


    I will give here the final version of docker-compose.yml if someone is too lazy to go into rep:


    version: "3.5"
    services:
        php:
          image: litepubl/php70:latest
          container_name: ${FPM_CONTAINER_NAME}
          env_file: .env
          working_dir: /var/www/html
          volumes:
            - ..:/var/www/html
            - ./php/php-ini-overrides.ini:/etc/php/7.2/fpm/conf.d/99-overrides.ini
            - ${LOGS_DIR}:/logs
            - ssh:/root/.ssh
          depends_on:
            - postgres
            - mongo
          networks:
              # this network
              - default 
              # external network
              - second_default
          external_links:
            - ${EXTERNAL_NGINX}
        postgres:
          image: postgres:9.5
          container_name: ${POSTGRES_CONTAINER_NAME}
          env_file: .env
          ports:
            - ${POSTGRES_EXT_PORT}:5432
          working_dir: /var/www/html
          command: postgres -c logging_collector=on -c log_destination=stderr -c log_directory=/logs -c client_min_messages=notice -c log_min_messages=warning -c log_min_error_statement=warning -c log_min_duration_statement=0 -c log_statement=all -c log_error_verbosity=default
          volumes:
            - ..:/var/www/html
            - db:/var/lib/postgresql/data
            - ${LOGS_DIR}:/logs
        mongo:
          image: mongo:latest
          container_name: ${MONGO_CONTAINER_NAME}
          ports:
          - ${MONGO_EXTERNAL_PORT}:27017
          volumes:
            - mongo:/data/db
        webserver:
          image: litepubl/nginx
          container_name: ${NGINX_CONTAINER_NAME}
          working_dir: /var/www/html
          volumes:
              - ..:/var/www/html
              - ${LOGS_DIR}:/var/log/nginx/
              - ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf
          ports:
            - ${NGINX_EXT_PORT}:80
          depends_on:
            - php
    volumes:
      ssh:
      db:
      mongo:
    networks:
      default:
        driver: bridge
      second_default:
        external: true
    

    As you can see, nothing complicated and everything works. I will also give some useful commands that I designed in the form of bat files. Running codeception tests


    del tests\_output\*.* /f /q
    del tests\_output\debug\*.* /f /q
    del logs\debug.log
    cd docker
    @cls
    docker-compose exec php bash test.sh
    cd ..

    and test.sh itself


    vendor/bin/codecept run unit  --steps --html --debug>testlog.txt
    

    After this article, the arisen questions are able to be solved by official documentation on docker or other used components. That's all, good luck with the development.


    Also popular now: