Set up LNMP environment in WSL 2 with Docker on Windows

Web development on Windows used to be annoying, since the web environment has to be deploy on a remote server or a VM. But with the release of WSL 2 and the integration of the docker backend, thing goes simple now.

Before doing that…

This post assume that you have already got a WSL 2 distro installed. If you don’t or you have a WSL 1 distro, follow the guide on Microsoft Docs to get an install or upgrade.

Phyllali

Download then install the latest Docker Desktop Installer first. After that, launch the Docker Desktop App and go to settings, to ensure that the Use the WSL 2 based engine check box has been checked.

 Use the WSL 2 based engine
Use the WSL 2 based engine

Then go to Resources -> WSL INTEGRATION tab, make sure the distro you want to use is enabled.

WSL INTEGRATION
WSL INTEGRATION

Last, open the Windows Terminal and try to run docker command in it. If it works, you can now setup the LNMP environment.

Something else


Nginx

Pull the docker image of Nginx first.

docker pull nginx

Create a work directory for Nginx then.

cd ~
mkdir test-docker && cd test-docker
mkdir www && cd www

Now create a simple index.html for testing.

nano index.html
<html>
        <head>
                <title>Docker Nginx Testing</title>
        </head>
        <body>
                <div>
                        <h3>Hello, world!</h3>
                </div>
        </body>
</html>

CTRL + O then CTRL + X to save. And then go back to create a configuration file nginx-test.conf for Nginx.

cd ..
mkdir nginx && cd nginx
mkdir conf && cd conf
nano nginx-test.conf
server {
    listen       80;
    location / {
        root   /www;
        index  index.html index.htm;
    }
}

CTRL + O then CTRL + X to save.

Note, the root parameter in the conf file is not the path on your machine (Neither WSL path above), it’s the “virtual” path in docker container. Don’t worried about this path, we are goanna map the “physical” path to it when running the docker run command.

Finally, run the docker container. As you can see, with the parameter after -v, the “physical” path “/home/user/test-docker/www” was mounted in the specified path “/www” in the container and mapped the 80 port of container to the 8080 port of local machine.

docker run --name nginx -p 8080:80 -v /home/user/test-docker/nginx/conf:/etc/nginx/conf.d -v /home/user/test-docker/www:/www nginx

Try accessing the localhost:8080 from your windows browser. You should see the hello world page now.


PHP

Unlike the Nginx, PHP version maters a lot. So, we are goanna specify the version of PHP image.

docker pull php:7.1-fpm

Then crate the work folder for PHP. The folder etc is to store the config file for PHP, named php.ini.

cd ~
cd test-docker
mkdir php && cd php
mkdir etc

To integrate MySQL to PHP later, the PHP installation must have the pdo_mysql extension installed. So, we need to write a Dockerfile to build our own PHP image. Before doing that, we need a PHP config file storing locally first. Since it’s hard to write a PHP config file manually, we are going to start the PHP container first then copy the default config file out of that.

docker run --name php -d php:7.1-fpm
docker cp php:/usr/local/etc/php/php.ini-development ./etc/php.ini

Then write the Dockerfile. The first line indicates that the new docker image to build is based on the existing image named “php:7.1-fpm”, and the last line means that pdo_mysql extension is to be installed.

nano Dockerfile
FROM php:7.1-fpm
COPY ./etc/php.ini /usr/local/etc/php/php.ini
RUN docker-php-ext-install pdo_mysql

CTRL + O then CTRL + X to save.

Now build our own PHP image, and name it as “php:my-php”.

docker build -t php:my-php .

And now we are able to start the container.

docker run --name php -p 9000:9000 -v /home/user/test-docker/www:/www php:my-php

Connect Nginx and PHP

Nginx don’t know how to handle a .php file, so we need to tell Nginx to send the .php file to PHP when finding one.

Crate a network bridge to link the two containers first. Then connect them to the same network bridge.

docker network create --driver bridge lnmp
docker network connect lnmp php
docker network connect lnmp nginx

After that, changing the Nginx config file to forward php script to php-fpm. Because we have already connected the two containers to a same “network”, so it’s ok for Nginx to find php-fpm by container name.

cd ~
cd test-docker
cd nginx
cd conf
nano nginx-test.conf
server {
    listen       80;
    location / {
        root   /www;
        index  index.php index.html index.htm;
    }
    location ~ \.php$ {
        root           /www;
        fastcgi_pass   php:9000;
        fastcgi_index  index.php;
        fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
        include        fastcgi_params;
    }
}

Reload the config file then.

docker exec -it nginx nginx -s reload

After reloading the config file, go to www folder to crate phpinfo.php file testing if the php-fpm working properly.

cd ~
cd test-docker
cd www
nano phpinfo.php
<?php
phpinfo();

CTRL + O then CTRL + X to save. Try accessing the localhost:8080/phpinfo.php from your windows browser. You should see the PHP Version page now.


MySQL

Pull the docker image of MySQL first, since MySQL version matters a lot, specify the version of MySQL image is necessary too.

docker pull mysql:5.7

Then crate the work folder for MySQL.

cd ~
cd test-docker
mkdir mysql && cd mysql

Crate a database file for testing.

nano test.sql
create table student(
        id int not null primary key auto_increment comment 'KEY',
        name varchar(20) not null comment 'NAME',
        age int not null comment 'AGE'
)engine=InnoDB default charset=utf8 comment 'STUDENTTABLE';
 
insert into student(name, age) values('A', 23);
insert into student(name, age) values('B', 18);

Next, we need to write a Dockerfile to config the password, database name, and copy SQL file inside.

FROM mysql:5.7
ENV MYSQL_ROOT_PASSWORD=123456 MYSQL_DATABASE=test
COPY ./test.sql /var/data/test.sql

Now build our custom MySQL image.

docker build -t mysql:my-mysql .

Then start the container, and attach it to lnmp network.

docker run --name mysql --network lnmp -p 3306:3306 mysql:my-mysql

Then enter the container, and import the test.sql to the database. Enter “123456” as password after running the second command.

docker exec -it mysql bash
mysql -u root -p
mysql> use test
mysql> source /var/data/test.sql

Then crate testmysql.php at the www folder for testing the MySQL server.

cd ~
cd test-docker
cd www
nano testmysql.php
<?php
$dsn = 'mysql:dbname=test;host=mysql';
$user = 'root';
$password = '123456';
 
try {
    $dbh = new PDO($dsn, $user, $password);
    $sql = 'SELECT * FROM student where id=?';
    $sth = $dbh->prepare($sql);
    $sth->execute([2]);
    $result = $sth->fetch(PDO::FETCH_ASSOC);
    var_dump($result);
} catch (PDOException $e) {
    echo 'Error: ' . $e->getMessage();
}

CTRL + O then CTRL + X to save. Try accessing the localhost:8080/ testmysql.php from your windows browser. You should see the SQL output now.

The lnmp environment is now fully configured.

Leave a comment

Your email address will not be published. Required fields are marked *