We'll cover a few things:
- Expanding on our use of default environment variables
- Separating our compose configurations for multiple environments
Environment Variables
The first thing we'll do is expand on our default environment variables to take the MySQL database setup into account.
The first time the mysql container is spun up on a developer's machine, these defaults will be used to set the passwords and a database to use:
#!/usr/bin/env bash
# Set environment variables for dev
export APP_ENV=${APP_ENV:-local}
export APP_PORT=${APP_PORT:-80}
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}
COMPOSE="docker-compose"
# Rest of bash script truncated for brevity...
And then we can update our docker-compose.yml
file, in the mysql
service (file truncated for brevity):
version: '2'
services:
mysql:
extends:
file: docker-compose.base.yml
service: mysql
ports:
- "${DB_PORT}:3306"
environment:
MYSQL_ROOT_PASSWORD: "${DB_ROOT_PASS}"
MYSQL_DATABASE: "${DB_NAME}"
MYSQL_USER: "${DB_USER}"
MYSQL_PASSWORD: "${DB_PASS}"
Docker-Compose for Dev and CI
For CI, we don't want any port binding. In fact, we may not want to even spin up a MySQL container. This, of course, is the opposite of what we want for local development. So, let's accomplish changing how we run Docker in devopment vs continuous integration.
File docker-compose.base.yml
We first create a base compose file that other files will extend
:
version: '2'
services:
app:
build:
context: ./docker/app
dockerfile: Dockerfile
image: shippingdocker.com/app
volumes:
- .:/var/www/html
networks:
- sdnet
node:
build:
context: ./docker/node
dockerfile: Dockerfile
image: shippingdocker.com/node
volumes:
- .:/var/www/html
networks:
- sdnet
mysql:
image: mysql:5.7
volumes:
- mysqldata:/var/lib/mysql
networks:
- sdnet
redis:
image: redis:alpine
volumes:
- redisdata:/data
networks:
- sdnet
File docker-compose.ci.yml
Then we can make a docker-compose file to extend this base file. Note we remove
version: '2'
services:
app:
extends:
file: docker-compose.base.yml
service: app
node:
extends:
file: docker-compose.base.yml
service: node
redis:
extends:
file: docker-compose.base.yml
service: redis
networks:
sdnet:
driver: "bridge"
volumes:
mysqldata:
driver: "local"
redisdata:
driver: "local"
File docker-compose.dev.yml
Finally we make our docker-compose file for development, which includes MySQL and our port bindings.
version: '2'
services:
app:
extends:
file: docker-compose.base.yml
service: app
ports:
- "${APP_PORT}:80"
node:
extends:
file: docker-compose.base.yml
service: node
mysql:
extends:
file: docker-compose.base.yml
service: mysql
ports:
- "${DB_PORT}:3306"
environment:
MYSQL_ROOT_PASSWORD: "${DB_ROOT_PASS}"
MYSQL_DATABASE: "${DB_NAME}"
MYSQL_USER: "${DB_USER}"
MYSQL_PASSWORD: "${DB_PASS}"
redis:
extends:
file: docker-compose.base.yml
service: redis
networks:
sdnet:
driver: "bridge"
volumes:
mysqldata:
driver: "local"
redisdata:
driver: "local"
Running Commands
We can see that running commands with these adds yet more boiler plate:
APP_PORT=80 docker-compose -f docker-compose.dev.yml ps
So let's update our develop
script to make use of these options:
Updating the develop
Script
The updated develop
script, which knows if we're in a Jenkins build (CI) and decides which compose file to run based on that:
#!/usr/bin/env bash
# Set environment variables for dev
export APP_ENV=${APP_ENV:-local}
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"
# Change settings for CI
if [ ! -z "$BUILD_NUMBER" ]; then
COMPOSE_FILE="ci"
fi
# Create docker-compose command to run
COMPOSE="docker-compose -f docker-compose.${COMPOSE_FILE}.yml"
# If we pass any arguments...
if [ $# -gt 0 ];then
# If "art" is used, pass-thru to "artisan"
# inside a new container
if [ "$1" == "art" ]; then
shift 1
$COMPOSE run --rm \
-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 \
-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 \
-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 \
-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 \
-w /var/www/html \
node \
./node_modules/.bin/gulp "$@"
# Else, pass-thru args to docker-compose
else
$COMPOSE "$@"
fi
else
$COMPOSE ps
fi
Testing It Out
We can see which is being selected using the config
command with docker-compose
:
# Make sure it's basically working
./develop ps
# See that it defaults to "dev" - we'll see
# the mysql service and our port bindings
./develop config
# Make sure we can force it to use the CI config
# We'll see no port bindings and no mysql service
BUILD_NUMBER=1234 ./develop config