Docker Compose Details for CI (and faster tests!)


We add some details to our helper script to properly run within Jenkins.

When we use Jenkins for CI, we will run into errors when running phpunit.

This is related to Docker assigning a pseudo-tty with docker-compose run commands. The way Jenkins runs jobs (essentially through shell commands we define), the TTY allocation fails, and thus the Jenkins job also fails, before we even run our test suite.

TTY Allocation

So, in our CI context, we need to stop doing that via the -T flag.

Let's update our develop script to do that

  1. We create a TTY variable
  2. TTY is set to -T when the CI compose file is used
  3. The $TTY option is appended to each run command
#!/usr/bin/env bash

# Set environment variables for dev or CI
export APP_PORT=${APP_PORT:-80}
export DB_PORT=${DB_PORT:-3306}
export DB_ROOT_PASS=${DB_ROOT_PASS:-secret}
export DB_NAME=${DB_NAME:-helpspot}
export DB_USER=${DB_USER:-helpspot}
export DB_PASS=${DB_PASS:-secret}

# Decide which docker-compose file to use
COMPOSE_FILE="dev"

# Disable pseudo-TTY allocation for CI (Jenkins)
TTY=""

if [ ! -z "$BUILD_NUMBER" ]; then
    COMPOSE_FILE="ci"
    TTY="-T"
fi

COMPOSE="docker-compose -f docker-compose.$COMPOSE_FILE.yml"

if [ $# -gt 0 ];then
    if [ "$1" == "art" ]; then
        shift 1
        $COMPOSE run --rm $TTY \
            -w /var/www/html \
            app \
            php artisan "$@"
    # If "composer" is used, pass-thru to "composer"
    # inside a new container
    elif [ "$1" == "composer" ]; then
        shift 1
        $COMPOSE run --rm $TTY \
            -w /var/www/html \
            app \
            composer "$@"

    # If "test" is used, run unit tests,
    # pass-thru any extra arguments to php-unit
    elif [ "$1" == "test" ]; then
        shift 1
        $COMPOSE run --rm $TTY \
            -w /var/www/html \
            app \
            ./vendor/bin/phpunit "$@"

    # If "npm" is used, run npm
    # from our node container
    elif [ "$1" == "npm" ]; then
        shift 1
        $COMPOSE run --rm $TTY \
            -w /var/www/html \
            node \
            npm "$@"

    # If "gulp" is used, run gulp
    # from our node container
    elif [ "$1" == "gulp" ]; then
        shift 1
        $COMPOSE run --rm $TTY \
            -w /var/www/html \
            node \
            ./node_modules/.bin/gulp "$@"
    else
        $COMPOSE "$@"
    fi
else
    $COMPOSE ps
fi

We can see how this appears differently when testing it out:

# We get colored output
./develop test

# We do not get colored output
BUILD_NUMBER=1234 ./develop test

Testing Helper

Along the same vain (testing), we can speed up our tests by not having Docker spin up a new container each time we run phpunit. Instead, if we have our containers running, we can use the existing app service.

This makes use of the exec command instead of the run command:

The new command:

elif [ "$1" == "t" ]; then
    shift 1
    $COMPOSE exec \
        app \
        sh -c "cd /var/www/html && ./vendor/bin/phpunit $@"

Here's the develop script in its final form with the ./develop t command, which runs phpunit via docker-compose exec:

#!/usr/bin/env bash

# Set environment variables for dev or CI
export APP_PORT=${APP_PORT:-80}
export DB_PORT=${DB_PORT:-3306}
export DB_ROOT_PASS=${DB_ROOT_PASS:-secret}
export DB_NAME=${DB_NAME:-helpspot}
export DB_USER=${DB_USER:-helpspot}
export DB_PASS=${DB_PASS:-secret}

# Decide which docker-compose file to use
COMPOSE_FILE="dev"

# Disable pseudo-TTY allocation for CI (Jenkins)
TTY=""

if [ ! -z "$BUILD_NUMBER" ]; then
    COMPOSE_FILE="ci"
    TTY="-T"
fi

COMPOSE="docker-compose -f docker-compose.$COMPOSE_FILE.yml"

if [ $# -gt 0 ];then
    if [ "$1" == "art" ]; then
        shift 1
        $COMPOSE run --rm $TTY \
            -w /var/www/html \
            app \
            php artisan "$@"
    # If "composer" is used, pass-thru to "composer"
    # inside a new container
    elif [ "$1" == "composer" ]; then
        shift 1
        $COMPOSE run --rm $TTY \
            -w /var/www/html \
            app \
            composer "$@"

    # If "test" is used, run unit tests,
    # pass-thru any extra arguments to php-unit
    elif [ "$1" == "test" ]; then
        shift 1
        $COMPOSE run --rm $TTY \
            -w /var/www/html \
            app \
            ./vendor/bin/phpunit "$@"

    elif [ "$1" == "t" ]; then
        shift 1
        $COMPOSE exec \
            app \
            sh -c "cd /var/www/html && ./vendor/bin/phpunit $@"

    # If "npm" is used, run npm
    # from our node container
    elif [ "$1" == "npm" ]; then
        shift 1
        $COMPOSE run --rm $TTY \
            -w /var/www/html \
            node \
            npm "$@"

    # If "gulp" is used, run gulp
    # from our node container
    elif [ "$1" == "gulp" ]; then
        shift 1
        $COMPOSE run --rm $TTY \
            -w /var/www/html \
            node \
            ./node_modules/.bin/gulp "$@"
    else
        $COMPOSE "$@"
    fi
else
    $COMPOSE ps
fi

And we can test this out:

./develop up -d

# Takes ~5.370 seconds
time ./develop test

# Takes ~3.953
# A few seconds (ish) quicker!
time ./develop t