diff --git a/bob/devtools/data/base-condarc b/bob/devtools/data/base-condarc
index c963517708ecb9f190e716c889c16ba3317b0e38..480c551df17ff2e90f5d07083bb99c07a61b1ce8 100644
--- a/bob/devtools/data/base-condarc
+++ b/bob/devtools/data/base-condarc
@@ -10,5 +10,3 @@ quiet: true #!final
 show_channel_urls: true #!final
 anaconda_upload: false #!final
 ssl_verify: false #!final
-channels:
-  - defaults
diff --git a/ci/bootstrap.py b/ci/bootstrap.py
new file mode 100755
index 0000000000000000000000000000000000000000..824b159b60ca1c1c35c96034716f69fe9b69e30a
--- /dev/null
+++ b/ci/bootstrap.py
@@ -0,0 +1,286 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+'''Bootstraps a new conda installation and prepares base environment
+
+If "local" is passed as parameter, then self installs an already built version
+of bob.devtools available on your conda-bld directory. If you pass "beta", then
+it bootstraps from the package installed on our conda beta channel.  If you
+pass "stable", then it bootstraps installing the package available on the
+stable channel.
+
+If bootstrapping anything else than "build", then provide a second argument
+with the name of the environment that one wants to create with an
+installation of bob.devtools.
+'''
+
+import os
+import sys
+import glob
+import shutil
+import platform
+import subprocess
+
+import logging
+logger = logging.getLogger('bootstrap')
+
+
+def run_cmdline(cmd, env=None):
+  '''Runs a command on a environment, logs output and reports status
+
+
+  Parameters:
+
+    cmd (list): The command to run, with parameters separated on a list
+
+    env (dict, Optional): Environment to use for running the program on. If not
+      set, use :py:obj:`os.environ`.
+
+
+  Returns:
+
+    str: The standard output and error of the command being executed
+
+  '''
+
+  if env is None: env = os.environ
+
+  logger.info('$ %s' % ' '.join(cmd))
+
+  start = time.time()
+  out = b''
+
+  p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
+      env=env)
+
+  chunk_size = 1 << 13
+  lineno = 0
+  for chunk in iter(lambda: p.stdout.read(chunk_size), b''):
+    decoded = chunk.decode()
+    while '\n' in decoded:
+      pos = decoded.index('\n')
+      print('%03d: %s' % (lineno, decoded[:pos]))
+      decoded = decoded[pos+1:]
+      lineno += 1
+    out += chunk
+
+  if p.wait() != 0:
+    logger.error('Command output is:\n%s', out.decode())
+    raise RuntimeError("command `%s' exited with error state (%d)" % \
+        (' '.join(cmd_log), p.returncode))
+
+  total = time.time() - start
+
+  logger.info('command took %s' % human_time(total))
+
+  out = out.decode()
+
+  return out
+
+
+def touch(path):
+  '''Python-implementation of the "touch" command-line application'''
+
+  with open(path, 'a'):
+    os.utime(path, times)
+
+
+def merge_conda_cache(cache, prefix):
+  '''Merges conda pkg caches and conda-bld folders'''
+
+  pkgs_dir = os.path.join(prefix, 'pkgs')
+  if not os.path.exists(pkgs_dir):
+    logger.info('mkdir -p %s', pkgs_dir)
+    os.makedirs(pkgs_dir)
+    pkgs_urls_txt = os.path.join(pkgs_dir, 'urls.txt')
+    logger.info('touch %s', pkgs_urls_txt)
+    touch(pkgs_urls_txt)
+
+  # move packages on cache/pkgs to pkgs_dir
+  cached_pkgs_dir = os.path.join(cache, 'pkgs')
+  cached_packages = glob.glob(os.path.join(cache_pkgs_dir, '*.tar.bz2'))
+  cached_packages = [k for k in cached_packages if not \
+      k.startswith(os.environ['CI_PROJECT_NAME'] + '-')]
+  logger.info('Merging %d cached conda packages', len(cached_packages))
+  for k in cached_packages:
+    dst = os.path.join(pkgs_dir, os.path.basename(k))
+    logger.debug('(move) %s -> %s', k, dst)
+    os.rename(k, dst)
+
+  # merge urls.txt files
+  logger.info('Merging urls.txt files from cache...')
+  urls = []
+  cached_pkgs_urls_txt = os.path.join(cached_pkgs_dir, 'urls.txt')
+  with open(pkgs_urls_txt, 'rb') as f1, \
+      open(cached_pkgs_urls_txt, 'rb') as f2:
+    data = set(f1.readlines() + f2.readlines())
+    data = sorted(list(data))
+  with open(pkgs_urls_txt, 'wb') as f:
+    f.writelines(data)
+
+  pkgs_urls = os.path.join(pkgs_dir, 'urls')
+  touch(pkgs_urls)
+
+  # move conda-bld build results
+  cached_conda_bld = os.path.join(cache, 'conda-bld')
+  if os.path.exists(cached_conda_bld):
+    dst = os.path.join(prefix, 'conda-bld')
+    logger.info('(move) %s -> %s', cached_conda_bld, dst)
+    os.rename(cached_conda_bld, dst)
+
+
+def get_miniconda_sh():
+  '''Retrieves the miniconda3 installer for the current system'''
+
+  import http.client
+
+  server = 'https://repo.continuum.io'
+  path = '/miniconda/Miniconda3-latest-%s-x86_64.sh'
+  if platform.system() == 'Darwin':
+    path = path % 'MacOSX'
+  else:
+    path = path % 'Linux'
+
+  logger.info('Requesting for %s%s...', server, path)
+  conn = http.client.HTTPSConnection(server)
+  conn.request("GET", path)
+  r1 = conn.getresponse()
+
+  assert r1.status == 200, 'Request for %s%s - returned status %d (%s)' % \
+      (server, path, r1.status, r1.reason)
+
+  dst = 'miniconda.sh'
+  logger.info('(download) %s%s -> %s...', server, path, dst)
+  with open(dst, 'wb') as f:
+    f.write(r1.read())
+
+
+def install_miniconda(prefix):
+  '''Creates a new miniconda installation'''
+
+  logger.info("Installing miniconda in %s...", prefix)
+
+  if not os.path.exists('miniconda.sh'):  #re-downloads installer
+    get_miniconda_sh()
+  else:
+    logger.info("Re-using cached miniconda3 installer")
+
+  if os.path.exists(prefix):  #this is the previous cache, move it
+    cached = prefix + '.cached'
+    logger.info('(move) %s -> %s', prefix, cached)
+    os.rename(prefix, cached)
+
+  run_cmdline(['bash', 'miniconda.sh', '-b', '-p', prefix])
+  merge_conda_cache(cached, prefix)
+  shutil.rmtree(cached)
+
+
+def get_local_channels():
+  '''Returns the relevant conda channels to consider if building project'''
+
+  # add channels
+  public = os.environ['CI_PROJECT_VISIBILITY'] == 'public'
+  stable = os.environ.get('CI_COMMIT_TAG') is not None
+
+  server = "http://www.idiap.ch"
+  channels = []
+
+  if not public:
+    if not stable:  #allowed private channels
+      channels += [server + '/private/conda/label/beta']  #allowed betas
+    channels += [server + '/private/conda']
+  if not stable:
+    channels += [server + '/public/conda/label/beta']  #allowed betas
+  channels += [server + '/public/conda']
+
+  return channels
+
+
+def add_channels_condarc(channels, condarc):
+  '''Appends passed channel list to condarc file, print contents'''
+
+  with open(condarc, 'at') as f:
+    f.write('channels:\n')
+    for k in channels:
+      f.write('  - %s\n' % k)
+
+  with open(condarc, 'rt') as f:
+    logger.info('Contents of $CONDARC:\n%s', f.read())
+
+
+if __name__ == '__main__':
+
+  if len(sys.argv) == 1:
+    print('usage: python3 %s build|local|beta|stable [name]' % sys.argv[0])
+    print('  build   to build bob.devtools')
+    print('  local   to bootstrap deploy|pypi stages for bob.devtools builds')
+    print('  beta    to bootstrap CI environment for beta builds')
+    print('  stable  to bootstrap CI environment for stable builds')
+    print('  [name]  (optional) if command is one of local|beta|stable, ')
+    print('          provide the name of env for bob.devtools installation')
+    sys.exit(1)
+
+  prefix = os.environ['CONDA_ROOT']
+  logger.info('os.environ["%s"] = %s', 'CONDA_ROOT', prefix)
+
+  workdir = os.environ['CI_PROJECT_DIR']
+  logger.info('os.environ["%s"] = %s', 'CI_PROJECT_DIR', workdir)
+
+  condarc = os.path.join(prefix, 'condarc')
+  os.environ['CONDARC'] = condarc
+  logger.info('os.environ["%s"] = %s', 'CONDARC', condarc)
+
+  conda_bin = os.path.join(prefix, 'bin', 'conda')
+  if not os.path.exists(conda_bin):
+    install_miniconda(prefix)
+
+  # creates the condarc file
+  baserc = os.path.join(workdir, 'bob', 'devtools', 'data', 'base-condarc')
+  logger.info('(copy) %s -> %s', baserc, condarc)
+  shutil.copy2(baserc, condarc)
+
+  conda_version = '4'
+  conda_build_version = '3'
+
+  if sys.argv[1] == 'build':
+
+    # simple - just use the defaults channels when self building
+    add_channels_condarc(['defaults'], condarc)
+    run_cmdline([conda_bin, 'install', '-n', 'base',
+      'python',
+      'conda=%s' % conda_version,
+      'conda-build=%s' % conda_build_version,
+      ])
+
+  elif sys.argv[1] == 'local':
+
+    # index the locally built packages
+    run_cmdline([conda_bin, 'install', '-n', 'base',
+      'python',
+      'conda=%s' % conda_version,
+      'conda-build=%s' % conda_build_version,
+      ])
+    conda_bld_path = os.path.join(prefix, 'conda-bld')
+    run_cmdline([conda_bin, 'index', conda_bld_path])
+    # add the locally build directory before defaults, boot from there
+    add_channels_condarc([conda_bld_path, 'defaults'], condarc)
+    run_cmdline([conda_bin, 'create', '-n', sys.argv[2], 'bob.devtools'])
+
+  elif sys.argv[1] in ('beta', 'stable'):
+
+    # installs from channel
+    channels = get_local_channels()
+    add_channels_condarc(channels + ['defaults'], condarc)
+    run_cmdline([conda_bin, 'create', '-n', sys.argv[2], 'bob.devtools'])
+
+  else:
+
+    logger.error("Bootstrap with 'build', or 'local|beta|stable <name>'")
+    logger.error("The value '%s' is not currently supported", sys.argv[1])
+    sys.exit(1)
+
+  # clean up
+  run_cmdline([conda_bin, 'clean', '--lock'])
+
+  # print conda information for debugging purposes
+  run_cmdline([conda_bin, 'info'])