2

I have a PHP project on GitHub for which I would like to build versioned Docker images. Kinda like CoreOS Container Linux does with their alpha, beta and stable release channels.

For development I follow the Git Flow principle and use the feature -> develop -> master branching.

I have hooked up Travis for automated PHPUnit testing and added CircleCI today as I found a Rakefile which could automatically generate a version. Which is used as a tag.

My current .travis.yml:

---
services:
  - docker
sudo: required
env:
  global:
    - ...

addons:
  jwt:
    secure: ...
cache:
  directories:
    - /home/travis/docker/
before_install:
  - "docker --version"
  - 'if [[ ${TRAVIS_BRANCH} == "master" ]] && [[ ${TRAVIS_PULL_REQUEST} == "false" ]]; then echo "ENV GIT_SHA ${TRAVIS_COMMIT::8}" >> Dockerfile; fi'
  - 'if [[ ${TRAVIS_BRANCH} == "master" ]] && [[ ${TRAVIS_PULL_REQUEST} == "false" ]] && [[ -f ${DOCKER_CACHE_FILE} ]]; then gunzip -c ${DOCKER_CACHE_FILE} | docker load; fi'
before_script:
  - "env > .env"
install:
  - 'if [[ ${TRAVIS_BRANCH} == "master" ]] && [[ ${TRAVIS_PULL_REQUEST} == "false" ]]; then docker build -t ${DOCKER_REPOSITORY}:${TRAVIS_COMMIT::8} --pull=true .; fi'
language: php
notifications:
  slack:
    secure: ...
php:
  - "5.6"
script:
  - "phpunit --bootstrap tests/bootstrap.php --testdox tests --coverage-text"
  - 'if [[ ${TRAVIS_BRANCH} == "master" ]] && [[ ${TRAVIS_PULL_REQUEST} == "false" ]]; then mkdir -p $(dirname ${DOCKER_CACHE_FILE}) ; docker save $(docker history -q ${DOCKER_REPOSITORY}:${TRAVIS_COMMIT::8} | grep -v "<missing>") | gzip > ${DOCKER_CACHE_FILE}; fi'
after_success:
  - 'if [[ $TRAVIS_PHP_VERSION == "5.6" ]] && [[ $TRAVIS_BRANCH == "develop" ]] && [[ $TRAVIS_PULL_REQUEST == "false" ]]; then sh generate-api.sh; fi'
  - 'if [[ ${TRAVIS_BRANCH} == "master" ]] && [[ ${TRAVIS_PULL_REQUEST} == "false" ]]; then docker login -u ${DOCKER_USERNAME} -p ${DOCKER_PASSWORD}; fi'
  - 'if [[ ${TRAVIS_BRANCH} == "master" ]] && [[ ${TRAVIS_PULL_REQUEST} == "false" ]]; then docker tag ${DOCKER_REPOSITORY}:${TRAVIS_COMMIT::8} ${DOCKER_REPOSITORY}:${TRAVIS_COMMIT::8}; fi'
  - 'if [[ ${TRAVIS_BRANCH} == "master" ]] && [[ ${TRAVIS_PULL_REQUEST} == "false" ]]; then docker tag ${DOCKER_REPOSITORY}:${TRAVIS_COMMIT::8} ${DOCKER_REPOSITORY}:travis-${TRAVIS_BUILD_NUMBER}; fi'
  - 'if [[ ${TRAVIS_BRANCH} == "master" ]] && [[ ${TRAVIS_PULL_REQUEST} == "false" ]]; then docker tag ${DOCKER_REPOSITORY}:${TRAVIS_COMMIT::8} ${AWS_ACCOUNT_NUMBER}.dkr.ecr.eu-west-1.amazonaws.com/${ECR_REPOSITORY}:${TRAVIS_COMMIT::8}; fi'
  - 'if [[ ${TRAVIS_BRANCH} == "master" ]] && [[ ${TRAVIS_PULL_REQUEST} == "false" ]]; then docker push ${DOCKER_REPOSITORY}; fi'
  - 'if [[ ${TRAVIS_BRANCH} == "master" ]] && [[ ${TRAVIS_PULL_REQUEST} == "false" ]]; then pip install --user awscli; fi'
  - 'if [[ ${TRAVIS_BRANCH} == "master" ]] && [[ ${TRAVIS_PULL_REQUEST} == "false" ]]; then export PATH=$PATH:$HOME/.local/bin; fi'
  - 'if [[ ${TRAVIS_BRANCH} == "master" ]] && [[ ${TRAVIS_PULL_REQUEST} == "false" ]]; then eval $(aws ecr get-login --region eu-west-1); fi'
  - 'if [[ ${TRAVIS_BRANCH} == "master" ]] && [[ ${TRAVIS_PULL_REQUEST} == "false" ]]; then docker push ${AWS_ACCOUNT_NUMBER}.dkr.ecr.eu-west-1.amazonaws.com/${ECR_REPOSITORY}:${TRAVIS_COMMIT::8}; fi'

And my current circle.yml:

---
machine:
  services:
    - docker
  php:
    version: 5.6.22

dependencies:
  override:
    - docker info
    - gem install httparty
    - rake build

test:
  override:
    - mkdir -p $CIRCLE_TEST_REPORTS/phpunit
    - phpunit --bootstrap tests/bootstrap.php --log-junit $CIRCLE_TEST_REPORTS/phpunit/junit.xml tests
    - docker run -d -p 9000:9000 storecore/php:latest; sleep 10
    - nc -z -w5 localhost 9000

And the used Rakefile:

require 'rake'
require 'httparty'
require 'json'

# Read the base version from VERSION file.
def version
  file = File.readlines('./version.php')
  v = file[1].scan(/'([^']*)'/)
  v[1].join(",")
end

# The name of the container
def container_name
  'php'
end

# The username the container is pushed to on DockerHub
def username
  'storecore'
end

# Get the latest version for the given base version provided by #version
def hub_version
  base = version
  taginfo = JSON.parse(HTTParty.get("https://hub.docker.com/v2/repositories/#{username}/#{container_name}/tags/").body)['results']

  return { base: base, build: nil } if taginfo.nil?
  tags = []
  taginfo.each do |tag|
    tags << tag['name']
  end
  current_base = tags.grep(/#{base}/)
  return { base: base, build: nil } if current_base.empty?
  build = current_base.sort { |x, y|
    a = x.split('.')[base.split('.').count].to_i
    b = y.split('.')[base.split('.').count].to_i
    a <=> b
  }.last.split('.').last.to_i
  { base: base, build: build }
end

# return current hub version for the current base
def latest_hub_version
  latest = hub_version
  "#{latest[:base]}.#{latest[:build]}"
end

# return the next version for the current base
def next_version
  latest = hub_version
  base = version
  build = latest[:build] || -1
  build += 1
  "#{base}.#{build}"
end

task :install_deps do
  sh 'gem install bundler'
  sh 'bundle install'
end

desc 'login into Docker Hub'
task :login do
  sh "docker login -u #{ENV['DOCKER_USER']} -p #{ENV['DOCKER_PASS']}"
end

desc 'tags latest as next_version'
task :tag => :login do
  sh "docker tag #{username}/#{container_name}:latest #{username}/#{container_name}:#{next_version}"
end

desc 'pushes the next_version and latest to docker hub'
task :push => :tag do
  sh "docker push #{username}/#{container_name}:#{next_version}"
  sh "docker push #{username}/#{container_name}:latest"
end

desc 'builds as latest'
task :build => :install_deps do
  sh "docker build --rm=false -t #{username}/#{container_name}:latest ."
end

task default: [:build, :push]

But in the end both solutions does not provide me a Docker image per release channel and are limited in their own way. It looks hacky to me. I bet there are better approaches or tools I could use. I don't mind switching to something else completely.

What would be the best approach to accomplish something like what CoreOS does?

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Browse other questions tagged or ask your own question.