Monday, 11 January 2021

Tweaks I made to my Bash environment in my Docker containers

G'day:

Intro

Please note that this is a sub-article of a larger body of work that is an exercise in setting up a Vue.js-driven website backed by PHP8 and MariaDB running in Docker containers. All of this is completely new to me, so is a learning exercise, rather than some exposition of my wisdom (which I have none of). I initially intended the whole exercise to be a single article, but by the time I had finished the first two sections, it was way too long for a single read, so I've split it into the following sections, each as their own article:

  1. Intro / Nginx
  2. PHP
  3. PHPUnit
  4. Tweaks I made to my Bash environment in my Docker containers (this article)
  5. MariaDB
  6. Installing Symfony
  7. Using Symfony
  8. Testing a simple web page built with Vue.js using Mocha, Chai and Puppeteer
  9. Refactoring the simple web page into Vue components (URL TBC)

As indicated: this is the fourth article in the series, and - chronologically - follows on from Part 3: PHPUnit. That said, this article is reasonably stand-alone, so not sure if it's really necessary to read everything else first. The only real "cross over" is that I'm experimenting within Docker containers that I created in the earlier articles. I guess you can refer back to the other articles if anything I say here seems to be making assumptions you can't make.

Also please note that there is very little that is earthshattering here. It's more an exercise of me - as a relative *nix noob - getting stuff working how I've become accustomed to it. I discuss nothing tricky or advanced here.

Today's exercise

Whilst doing all the crap to get Nginx, PHP and PHPUnit working, I was spending an awful lot of time in and out of Bash, running various bits of code and testing stuff and the like. I found there were a few annoying things about running Bash in these containers:

  • The Debian distro that the PHP container uses doesn't include ping and other network utils I was needing to use.
  • Bash history works for the life of the container, but does not - obviously - live between rebuilds of a container. This was a pain in the butt as I had a bunch of things to run every rebuild to check stuff.
  • I needed to add a coupla aliases: ll which everyone has; and one, cls, that I've got that is a bit nicer than just clear

In this article I'm gonna work through implementing those, and a coupla other things.

Adding the networking utils was a one-liner in the docker/php-fpm/Dockerfile:

FROM php:8.0-fpm
RUN apt-get update
RUN apt-get install git --yes
RUN apt-get install net-tools iputils-ping --yes
RUN docker-php-ext-install pdo_mysql
RUN pecl install xdebug-3.0.1 && docker-php-ext-enable xdebug
COPY --from=composer /usr/bin/composer /usr/bin/composer
#COPY ./.bashrc /root/.bashrc
ENV XDEBUG_MODE=coverage
WORKDIR  /usr/share/fullstackExercise/
CMD composer install ; php-fpm
EXPOSE 9000

That just installs both net-tools and iputils-ping. The --yes just suppresses confirmation the installation asks, which breaks the Docker build if I don't answer it in a batch fashion: the Docker build process is not interactive.

Sorting out the Bash history was slightly trickier. My initial plan was to just mount a stubbed file in the container (this from docker/docker-compose.yml):

  php-fpm:
    build:
      context: ./php-fpm
    volumes:
      - ..:/usr/share/fullstackExercise
      - ./php-fpm/.bash_history:/root/.bash_history
    stdin_open: true # docker run -i
    tty: true        # docker run -t
    networks:
      - backend

This has some drawbacks. The build broke if ./php-fpm/.bash_history wasn't in the host file system. To guarantee it was there, I needed it in source control. But… by its very nature it's getting changed all the time, which gets annoying when I go to commit stuff. There's ways to work around this in git, but those were causing issues as well.

In the end I decided to take this approach:

    volumes:
      - ..:/usr/share/fullstackExercise
      - ./php-fpm/root_home:/root

That's the entire home directory for the root user. By default it has nothing in it, so I don't need to replicate much in the file system in source control: just make the directory exist, and .gitignore .bash_history:

adam@DESKTOP-QV1A45U:/mnt/c/src/fullstackExercise/docker$ tree -aF --dirsfirst -L 2 php-fpm/root_home/
php-fpm/root_home/
├── .bash_history*
└── .gitignore*

0 directories, 2 files

adam@DESKTOP-QV1A45U:/mnt/c/src/fullstackExercise/docker$ cat php-fpm/root_home/.gitignore
*
!.gitignore

adam@DESKTOP-QV1A45U:/mnt/c/src/fullstackExercise/docker$

And this just works! Below I show rebuilding my containers, going into bash on the PHP container, showing the empty history, doing some stuff then exiting. Then I repeat the whole operation and you can see the history is sticking across containers now.

adam@DESKTOP-QV1A45U:/mnt/c/src/fullstackExercise/docker$ docker-compose down --remove-orphans
Stopping docker_nginx_1   ... done
Stopping docker_php-fpm_1 ... done
Removing docker_nginx_1   ... done
Removing docker_php-fpm_1 ... done
Removing network docker_backend
adam@DESKTOP-QV1A45U:/mnt/c/src/fullstackExercise/docker$ docker-compose up --build --detach
Creating network "docker_backend" with driver "bridge"
Building nginx
[...]
Building php-fpm
[...]
Creating docker_nginx_1   ... done
Creating docker_php-fpm_1 ... done
adam@DESKTOP-QV1A45U:/mnt/c/src/fullstackExercise/docker$ docker exec --interactive --tty docker_php-fpm_1 /bin/bash
root@fd43b546b8c0:/usr/share/fullstackExercise# history
    1  history
root@fd43b546b8c0:/usr/share/fullstackExercise# cat ~/.bash_history
root@fd43b546b8c0:/usr/share/fullstackExercise# ls
LICENSE    _public        composer.lock  log        phpmd.xml    public  test
README.md  composer.json  docker         phpcs.xml  phpunit.xml  src     vendor
root@fd43b546b8c0:/usr/share/fullstackExercise# exit
exit
adam@DESKTOP-QV1A45U:/mnt/c/src/fullstackExercise/docker$ docker-compose down --remove-orphans
Stopping docker_php-fpm_1 ... done
Stopping docker_nginx_1   ... done
Removing docker_php-fpm_1 ... done
Removing docker_nginx_1   ... done
Removing network docker_backend
adam@DESKTOP-QV1A45U:/mnt/c/src/fullstackExercise/docker$ docker-compose up --build --detach
Creating network "docker_backend" with driver "bridge"
Building nginx
[...]
Building php-fpm
[...]
Creating docker_nginx_1   ... done
Creating docker_php-fpm_1 ... done
adam@DESKTOP-QV1A45U:/mnt/c/src/fullstackExercise/docker$ docker exec --interactive --tty docker_php-fpm_1 /bin/bash
root@9b7b60d6208a:/usr/share/fullstackExercise# history
    1  history
    2  cat ~/.bash_history
    3  ls
    4  exit
    5  history
root@9b7b60d6208a:/usr/share/fullstackExercise# cat ~/.bash_history
history
cat ~/.bash_history
ls
exit
root@9b7b60d6208a:/usr/share/fullstackExercise# exit
exit
adam@DESKTOP-QV1A45U:/mnt/c/src/fullstackExercise/docker$

Quite pleased with that, I am.

Given how I'm voluming-in the entire home directory, doing the aliases as easy enough, I just needed to drop a .bashrc into that docker/php-fpm/root_home directory. The image doesn't have a .bashrc by default, so this is fine.

alias ll='ls -alF'
alias cls='clear; printf "\033[3J"'

There's a coupla things to note here. Initially by accident I saved this file with CRLF line endings, and so the ll alias didn't work:

root@50fbd03160ef:/usr/share/fullstackExercise# ll
's: invalid option -- '
Try 'ls --help' for more information.
root@50fbd03160ef:/usr/share/fullstackExercise#

That's a trap for young players there (even old burnt-out ones like me, too). It was not immediately obvious what the issue is there, so it took a bit of googling and stack-overflow-ing to find the answer. Something to remember. But once one sorts that out, it works fine:

root@5962e5abd527:/usr/share/fullstackExercise# ll
total 173
drwxrwxrwx 1 1000 1000    512 Dec 14 20:14 ./
drwxr-xr-x 1 root root   4096 Dec 14 18:32 ../
drwxrwxrwx 1 1000 1000    512 Dec 14 19:03 .git/
-rwxrwxrwx 1 1000 1000     49 Dec 14 17:59 .gitignore*
drwxrwxrwx 1 1000 1000    512 Dec 14 20:14 .idea/
-rwxrwxrwx 1 1000 1000  35149 Dec 11 11:29 LICENSE*
-rwxrwxrwx 1 1000 1000    119 Dec 11 11:29 README.md*
-rwxrwxrwx 1 1000 1000    563 Dec 14 14:01 composer.json*
-rwxrwxrwx 1 1000 1000 123727 Dec 14 17:00 composer.lock*
drwxrwxrwx 1 1000 1000    512 Dec 14 19:10 docker/
drwxrwxrwx 1 1000 1000    512 Dec  5 13:19 log/
-rwxrwxrwx 1 1000 1000    479 Dec 14 17:00 phpcs.xml*
-rwxrwxrwx 1 1000 1000   1913 Dec 14 17:00 phpmd.xml*
-rwxrwxrwx 1 1000 1000    709 Dec 14 17:00 phpunit.xml*
drwxrwxrwx 1 1000 1000    512 Dec 14 17:00 public/
drwxrwxrwx 1 1000 1000    512 Dec 14 15:36 src/
drwxrwxrwx 1 1000 1000    512 Dec 14 17:00 test/
drwxrwxrwx 1 1000 1000    512 Dec 14 14:01 vendor/
root@5962e5abd527:/usr/share/fullstackExercise#

The cls alias I make just solves something that annoys me about clear is that it doesn't clear the scrollback history when SSHed into the box, so if one mouse-scrolls up, one can still move past where you cleared. This makes things hard to find sometimes, and defeats the purpose of clear IMO. The escape sequence in the alias alias cls='clear; printf "\033[3J"' just clears the scrollback as well as the screen. Or something that amounts to that anyhow. Shrug. I just copy & pasted it once upon a time, and still use it.

That's all I had for this one. Next stop: "Part 5: MariaDB".

Righto.

--
Adam