diff --git a/.gitignore b/.gitignore
index 330ee15b0b..5389aa8947 100644
--- a/.gitignore
+++ b/.gitignore
@@ -230,7 +230,7 @@ __pycache__/
 
 # Distribution / packaging
 .Python
-build/
+/build/
 develop-eggs/
 dist/
 downloads/
@@ -378,5 +378,7 @@ $RECYCLE.BIN/
 # Windows shortcuts
 *.lnk
 
+# Temporary scratch directory used by the tests
+tests/integration/buildcmd/scratch
 
 # End of https://www.gitignore.io/api/osx,node,macos,linux,python,windows,pycharm,intellij,sublimetext,visualstudiocode
diff --git a/requirements/base.txt b/requirements/base.txt
index 0dc002c2ee..e351d186eb 100644
--- a/requirements/base.txt
+++ b/requirements/base.txt
@@ -11,4 +11,4 @@ docker>=3.3.0
 dateparser~=0.7
 python-dateutil~=2.6
 pathlib2~=2.3.2; python_version<"3.4"
-
+aws_lambda_builders==0.0.2-dev
diff --git a/samcli/cli/command.py b/samcli/cli/command.py
index a659109c71..a85c97f808 100644
--- a/samcli/cli/command.py
+++ b/samcli/cli/command.py
@@ -16,6 +16,7 @@
     "samcli.commands.deploy",
     "samcli.commands.package",
     "samcli.commands.logs",
+    "samcli.commands.build"
 }
 
 
diff --git a/samcli/commands/_utils/__init__.py b/samcli/commands/_utils/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/samcli/commands/_utils/options.py b/samcli/commands/_utils/options.py
new file mode 100644
index 0000000000..270aac92bd
--- /dev/null
+++ b/samcli/commands/_utils/options.py
@@ -0,0 +1,118 @@
+"""
+Common CLI options shared by various commands
+"""
+
+import os
+import logging
+from functools import partial
+
+import click
+from samcli.cli.types import CfnParameterOverridesType
+
+_TEMPLATE_OPTION_DEFAULT_VALUE = "template.[yaml|yml]"
+
+
+LOG = logging.getLogger(__name__)
+
+
+def get_or_default_template_file_name(ctx, param, provided_value, include_build):
+    """
+    Default value for the template file name option is more complex than what Click can handle.
+    This method either returns user provided file name or one of the two default options (template.yaml/template.yml)
+    depending on the file that exists
+
+    :param ctx: Click Context
+    :param param: Param name
+    :param provided_value: Value provided by Click. It could either be the default value or provided by user.
+    :return: Actual value to be used in the CLI
+    """
+
+    search_paths = [
+        "template.yaml",
+        "template.yml",
+    ]
+
+    if include_build:
+        search_paths.insert(0, os.path.join(".aws-sam", "build", "template.yaml"))
+
+    if provided_value == _TEMPLATE_OPTION_DEFAULT_VALUE:
+        # Default value was used. Value can either be template.yaml or template.yml. Decide based on which file exists
+        # .yml is the default, even if it does not exist.
+        provided_value = "template.yml"
+
+        for option in search_paths:
+            if os.path.exists(option):
+                provided_value = option
+                break
+
+    result = os.path.abspath(provided_value)
+    LOG.debug("Using SAM Template at %s", result)
+    return result
+
+
+def template_common_option(f):
+    """
+    Common ClI option for template
+
+    :param f: Callback passed by Click
+    :return: Callback
+    """
+    return template_click_option()(f)
+
+
+def template_option_without_build(f):
+    """
+    Common ClI option for template
+
+    :param f: Callback passed by Click
+    :return: Callback
+    """
+    return template_click_option(include_build=False)(f)
+
+
+def template_click_option(include_build=True):
+    """
+    Click Option for template option
+    """
+    return click.option('--template', '-t',
+                        default=_TEMPLATE_OPTION_DEFAULT_VALUE,
+                        type=click.Path(),
+                        envvar="SAM_TEMPLATE_FILE",
+                        callback=partial(get_or_default_template_file_name, include_build=include_build),
+                        show_default=True,
+                        help="AWS SAM template file")
+
+
+def docker_common_options(f):
+    for option in reversed(docker_click_options()):
+        option(f)
+
+    return f
+
+
+def docker_click_options():
+
+    return [
+        click.option('--skip-pull-image',
+                     is_flag=True,
+                     help="Specify whether CLI should skip pulling down the latest Docker image for Lambda runtime.",
+                     envvar="SAM_SKIP_PULL_IMAGE"),
+
+        click.option('--docker-network',
+                     envvar="SAM_DOCKER_NETWORK",
+                     help="Specifies the name or id of an existing docker network to lambda docker "
+                          "containers should connect to, along with the default bridge network. If not specified, "
+                          "the Lambda containers will only connect to the default bridge docker network."),
+    ]
+
+
+def parameter_override_click_option():
+    return click.option("--parameter-overrides",
+                        type=CfnParameterOverridesType(),
+                        help="Optional. A string that contains CloudFormation parameter overrides encoded as key=value "
+                             "pairs. Use the same format as the AWS CLI, e.g. 'ParameterKey=KeyPairName,"
+                             "ParameterValue=MyKey ParameterKey=InstanceType,ParameterValue=t1.micro'")
+
+
+def parameter_override_option(f):
+    return parameter_override_click_option()(f)
diff --git a/samcli/commands/_utils/template.py b/samcli/commands/_utils/template.py
new file mode 100644
index 0000000000..f44e285ef0
--- /dev/null
+++ b/samcli/commands/_utils/template.py
@@ -0,0 +1,30 @@
+"""
+Utilities to manipulate template
+"""
+
+import yaml
+
+try:
+    import pathlib
+except ImportError:
+    import pathlib2 as pathlib
+
+from samcli.yamlhelper import yaml_parse
+
+
+def get_template_data(template_file):
+    """
+    Read the template file, parse it as JSON/YAML and return the template as a dictionary.
+
+    :param string template_file: Path to the template to read
+    :return dict: Template data as a dictionary
+    """
+
+    if not pathlib.Path(template_file).exists():
+        raise ValueError("Template file not found at {}".format(template_file))
+
+    with open(template_file, 'r') as fp:
+        try:
+            return yaml_parse(fp.read())
+        except (ValueError, yaml.YAMLError) as ex:
+            raise ValueError("Failed to parse template: {}".format(str(ex)))
diff --git a/samcli/commands/build/__init__.py b/samcli/commands/build/__init__.py
new file mode 100644
index 0000000000..12582b5c2b
--- /dev/null
+++ b/samcli/commands/build/__init__.py
@@ -0,0 +1,6 @@
+"""
+`sam build` command
+"""
+
+# Expose the cli object here
+from .command import cli  # noqa
diff --git a/samcli/commands/build/build_context.py b/samcli/commands/build/build_context.py
new file mode 100644
index 0000000000..4afe3f6abf
--- /dev/null
+++ b/samcli/commands/build/build_context.py
@@ -0,0 +1,123 @@
+"""
+Context object used by build command
+"""
+
+import os
+import shutil
+
+try:
+    import pathlib
+except ImportError:
+    import pathlib2 as pathlib
+
+from samcli.local.docker.manager import ContainerManager
+from samcli.commands.local.lib.sam_function_provider import SamFunctionProvider
+from samcli.commands._utils.template import get_template_data
+from samcli.commands.exceptions import UserException
+
+
+class BuildContext(object):
+
+    _BUILD_DIR_PERMISSIONS = 0o755
+
+    def __init__(self,
+                 template_file,
+                 base_dir,
+                 build_dir,
+                 manifest_path=None,
+                 clean=False,
+                 use_container=False,
+                 parameter_overrides=None,
+                 docker_network=None,
+                 skip_pull_image=False):
+
+        self._template_file = template_file
+        self._base_dir = base_dir
+        self._build_dir = build_dir
+        self._manifest_path = manifest_path
+        self._clean = clean
+        self._use_container = use_container
+        self._parameter_overrides = parameter_overrides
+        self._docker_network = docker_network
+        self._skip_pull_image = skip_pull_image
+
+        self._function_provider = None
+        self._template_dict = None
+        self._app_builder = None
+        self._container_manager = None
+
+    def __enter__(self):
+        try:
+            self._template_dict = get_template_data(self._template_file)
+        except ValueError as ex:
+            raise UserException(str(ex))
+
+        self._function_provider = SamFunctionProvider(self._template_dict, self._parameter_overrides)
+
+        if not self._base_dir:
+            # Base directory, if not provided, is the directory containing the template
+            self._base_dir = str(pathlib.Path(self._template_file).resolve().parent)
+
+        self._build_dir = self._setup_build_dir(self._build_dir, self._clean)
+
+        if self._use_container:
+            self._container_manager = ContainerManager(docker_network_id=self._docker_network,
+                                                       skip_pull_image=self._skip_pull_image)
+
+        return self
+
+    def __exit__(self, *args):
+        pass
+
+    @staticmethod
+    def _setup_build_dir(build_dir, clean):
+
+        # Get absolute path
+        build_dir = str(pathlib.Path(build_dir).resolve())
+
+        if not pathlib.Path(build_dir).exists():
+            # Build directory does not exist. Create the directory and all intermediate paths
+            os.makedirs(build_dir, BuildContext._BUILD_DIR_PERMISSIONS)
+
+        if os.listdir(build_dir) and clean:
+            # Build folder contains something inside. Clear everything.
+            shutil.rmtree(build_dir)
+            # this would have cleared the parent folder as well. So recreate it.
+            os.mkdir(build_dir, BuildContext._BUILD_DIR_PERMISSIONS)
+
+        return build_dir
+
+    @property
+    def container_manager(self):
+        return self._container_manager
+
+    @property
+    def function_provider(self):
+        return self._function_provider
+
+    @property
+    def template_dict(self):
+        return self._template_dict
+
+    @property
+    def build_dir(self):
+        return self._build_dir
+
+    @property
+    def base_dir(self):
+        return self._base_dir
+
+    @property
+    def use_container(self):
+        return self._use_container
+
+    @property
+    def output_template_path(self):
+        return os.path.join(self._build_dir, "template.yaml")
+
+    @property
+    def manifest_path_override(self):
+        if self._manifest_path:
+            return os.path.abspath(self._manifest_path)
+
+        return None
diff --git a/samcli/commands/build/command.py b/samcli/commands/build/command.py
new file mode 100644
index 0000000000..482fcc4c87
--- /dev/null
+++ b/samcli/commands/build/command.py
@@ -0,0 +1,151 @@
+"""
+CLI command for "build" command
+"""
+
+import os
+import logging
+import click
+
+from samcli.commands.exceptions import UserException
+from samcli.yamlhelper import yaml_dump
+from samcli.cli.main import pass_context, common_options as cli_framework_options, aws_creds_options
+from samcli.commands._utils.options import template_option_without_build, docker_common_options, \
+    parameter_override_option
+from samcli.commands.build.build_context import BuildContext
+from samcli.lib.build.app_builder import ApplicationBuilder, UnsupportedRuntimeException, \
+    BuildError, UnsupportedBuilderLibraryVersionError
+
+LOG = logging.getLogger(__name__)
+
+
+HELP_TEXT = """
+Use this command to build your AWS Lambda Functions source code to generate artifacts that can run on AWS Lambda.\n
+\b
+Supported Resource Types
+------------------------
+1. AWS::Serverless::Function\n
+2.AWS::Lambda::Function\n
+\b
+Supported Runtimes
+------------------
+1. Python2.7\n
+2. Python3.6\n
+\b
+Usage
+-----
+In your SAM template, specify the path to your function's source code
+in the resource's Code or CodeUri property.
+\b
+To build on your workstation, run this command in folder containing
+SAM template. Built artifacts will be written to .aws-sam/build folder
+$ sam build\n
+\b
+To build inside a AWS Lambda like Docker container
+$ sam build --use-container
+\b
+To build & run your functions locally
+$ sam build && sam local invoke
+\b
+To build and package for deployment
+$ sam build && sam package
+"""
+
+
+@click.command("build", help=HELP_TEXT, short_help="Build your Lambda function code")
+@click.option('--build-dir', '-b',
+              default=os.path.join(".aws-sam", "build"),
+              type=click.Path(file_okay=False, dir_okay=True, writable=True),  # Must be a directory
+              help="Path to a folder where the built artifacts will be stored")
+@click.option("--base-dir", "-s",
+              default=None,
+              type=click.Path(dir_okay=True, file_okay=False),  # Must be a directory
+              help="Resolve relative paths to function's source code with respect to this folder. Use this if "
+                   "SAM template and your source code are not in same enclosing folder. By default, relative paths to"
+                   "are resolved with respect to the SAM template's location")
+@click.option("--use-container",
+              is_flag=True,
+              help="Run the builds inside a Docker container that simulates an AWS Lambda like environment")
+@click.option("--manifest", "-m",
+              default=None,
+              type=click.Path(),
+              help="Path to a custom dependency manifest (ex: package.json) to use instead of the default one")
+@template_option_without_build
+@parameter_override_option
+@docker_common_options
+@cli_framework_options
+@aws_creds_options
+@pass_context
+def cli(ctx,
+        template,
+        base_dir,
+        build_dir,
+        use_container,
+        manifest,
+        docker_network,
+        skip_pull_image,
+        parameter_overrides):
+    # All logic must be implemented in the ``do_cli`` method. This helps with easy unit testing
+
+    do_cli(template, base_dir, build_dir, True, use_container, manifest, docker_network,
+           skip_pull_image, parameter_overrides)  # pragma: no cover
+
+
+def do_cli(template,  # pylint: disable=too-many-locals
+           base_dir,
+           build_dir,
+           clean,
+           use_container,
+           manifest_path,
+           docker_network,
+           skip_pull_image,
+           parameter_overrides):
+    """
+    Implementation of the ``cli`` method
+    """
+
+    LOG.debug("'build' command is called")
+
+    if use_container:
+        LOG.info("Starting Build inside a container")
+
+    with BuildContext(template,
+                      base_dir,
+                      build_dir,
+                      clean=clean,
+                      manifest_path=manifest_path,
+                      use_container=use_container,
+                      parameter_overrides=parameter_overrides,
+                      docker_network=docker_network,
+                      skip_pull_image=skip_pull_image) as ctx:
+
+        builder = ApplicationBuilder(ctx.function_provider,
+                                     ctx.build_dir,
+                                     ctx.base_dir,
+                                     manifest_path_override=ctx.manifest_path_override,
+                                     container_manager=ctx.container_manager
+                                     )
+        try:
+            artifacts = builder.build()
+            modified_template = builder.update_template(ctx.template_dict,
+                                                        ctx.output_template_path,
+                                                        artifacts)
+
+            with open(ctx.output_template_path, "w") as fp:
+                fp.write(yaml_dump(modified_template))
+
+            msg = """\nBuild Artifacts Available At: {artifacts_dir}
+
+Next Steps
+==========
+[*] Invoke Function: sam local invoke -t {template}
+[*] Package: sam package --template-file {template}
+            """.format(artifacts_dir=os.path.relpath(ctx.build_dir),
+                       template=os.path.relpath(ctx.output_template_path))
+
+            click.secho("\nBuild Succeeded", fg="green")
+
+            click.secho(msg, fg="yellow")
+
+        except (UnsupportedRuntimeException, BuildError, UnsupportedBuilderLibraryVersionError) as ex:
+            click.secho("Build Failed", fg="red")
+            raise UserException(str(ex))
diff --git a/samcli/commands/local/cli_common/invoke_context.py b/samcli/commands/local/cli_common/invoke_context.py
index 06f58e2a86..d003e154bc 100644
--- a/samcli/commands/local/cli_common/invoke_context.py
+++ b/samcli/commands/local/cli_common/invoke_context.py
@@ -4,18 +4,17 @@
 
 import errno
 import json
-import sys
 import os
-import yaml
 
 import docker
 import requests
 
-from samcli.yamlhelper import yaml_parse
+import samcli.lib.utils.osutils as osutils
 from samcli.commands.local.lib.local_lambda import LocalLambdaRunner
 from samcli.commands.local.lib.debug_context import DebugContext
 from samcli.local.lambdafn.runtime import LambdaRuntime
 from samcli.local.docker.manager import ContainerManager
+from samcli.commands._utils.template import get_template_data
 from .user_exceptions import InvokeContextException, DebugContextException
 from ..lib.sam_function_provider import SamFunctionProvider
 
@@ -202,15 +201,7 @@ def stdout(self):
         if self._log_file_handle:
             return self._log_file_handle
 
-        # We write all of the data to stdout with bytes, typically io.BytesIO. stdout in Python2
-        # accepts bytes but Python3 does not. This is due to a type change on the attribute. To keep
-        # this consistent, we leave Python2 the same and get the .buffer attribute on stdout in Python3
-        byte_stdout = sys.stdout
-
-        if sys.version_info.major > 2:
-            byte_stdout = sys.stdout.buffer  # pylint: disable=no-member
-
-        return byte_stdout
+        return osutils.stdout()
 
     @property
     def stderr(self):
@@ -222,15 +213,7 @@ def stderr(self):
         if self._log_file_handle:
             return self._log_file_handle
 
-        # We write all of the data to stdout with bytes, typically io.BytesIO. stderr in Python2
-        # accepts bytes but Python3 does not. This is due to a type change on the attribute. To keep
-        # this consistent, we leave Python2 the same and get the .buffer attribute on stderr in Python3
-        byte_stderr = sys.stderr
-
-        if sys.version_info.major > 2:
-            byte_stderr = sys.stderr.buffer  # pylint: disable=no-member
-
-        return byte_stderr
+        return osutils.stderr()
 
     @property
     def template(self):
@@ -275,14 +258,10 @@ def _get_template_data(template_file):
         :raises InvokeContextException: If template file was not found or the data was not a JSON/YAML
         """
 
-        if not os.path.exists(template_file):
-            raise InvokeContextException("Template file not found at {}".format(template_file))
-
-        with open(template_file, 'r') as fp:
-            try:
-                return yaml_parse(fp.read())
-            except (ValueError, yaml.YAMLError) as ex:
-                raise InvokeContextException("Failed to parse template: {}".format(str(ex)))
+        try:
+            return get_template_data(template_file)
+        except ValueError as ex:
+            raise InvokeContextException(str(ex))
 
     @staticmethod
     def _get_env_vars_value(filename):
diff --git a/samcli/commands/local/cli_common/options.py b/samcli/commands/local/cli_common/options.py
index 477f535d88..0d1c559f24 100644
--- a/samcli/commands/local/cli_common/options.py
+++ b/samcli/commands/local/cli_common/options.py
@@ -2,58 +2,8 @@
 Common CLI options for invoke command
 """
 
-import os
 import click
-from samcli.cli.types import CfnParameterOverridesType
-
-_TEMPLATE_OPTION_DEFAULT_VALUE = "template.[yaml|yml]"
-
-
-def get_or_default_template_file_name(ctx, param, provided_value):
-    """
-    Default value for the template file name option is more complex than what Click can handle.
-    This method either returns user provided file name or one of the two default options (template.yaml/template.yml)
-    depending on the file that exists
-
-    :param ctx: Click Context
-    :param param: Param name
-    :param provided_value: Value provided by Click. It could either be the default value or provided by user.
-    :return: Actual value to be used in the CLI
-    """
-
-    if provided_value == _TEMPLATE_OPTION_DEFAULT_VALUE:
-        # Default value was used. Value can either be template.yaml or template.yml. Decide based on which file exists
-        # .yml is the default, even if it does not exist.
-        provided_value = "template.yml"
-
-        option = "template.yaml"
-        if os.path.exists(option):
-            provided_value = option
-
-    return os.path.abspath(provided_value)
-
-
-def template_common_option(f):
-    """
-    Common ClI option for template
-
-    :param f: Callback passed by Click
-    :return: Callback
-    """
-    return template_click_option()(f)
-
-
-def template_click_option():
-    """
-    Click Option for template option
-    """
-    return click.option('--template', '-t',
-                        default=_TEMPLATE_OPTION_DEFAULT_VALUE,
-                        type=click.Path(),
-                        envvar="SAM_TEMPLATE_FILE",
-                        callback=get_or_default_template_file_name,
-                        show_default=True,
-                        help="AWS SAM template file")
+from samcli.commands._utils.options import template_click_option, docker_click_options, parameter_override_click_option
 
 
 def service_common_options(port):
@@ -104,11 +54,7 @@ def invoke_common_options(f):
                      type=click.Path(exists=True),
                      help="JSON file containing values for Lambda function's environment variables."),
 
-        click.option("--parameter-overrides",
-                     type=CfnParameterOverridesType(),
-                     help="Optional. A string that contains CloudFormation parameter overrides encoded as key=value "
-                          "pairs. Use the same format as the AWS CLI, e.g. 'ParameterKey=KeyPairName,"
-                          "ParameterValue=MyKey ParameterKey=InstanceType,ParameterValue=t1.micro'"),
+        parameter_override_click_option(),
 
         click.option('--debug-port', '-d',
                      help="When specified, Lambda function container will start in debug mode and will expose this "
@@ -128,19 +74,10 @@ def invoke_common_options(f):
                           "a remote machine, you must mount the path where the SAM file exists on the docker machine "
                           "and modify this value to match the remote machine."),
 
-        click.option('--docker-network',
-                     envvar="SAM_DOCKER_NETWORK",
-                     help="Specifies the name or id of an existing docker network to lambda docker "
-                          "containers should connect to, along with the default bridge network. If not specified, "
-                          "the Lambda containers will only connect to the default bridge docker network."),
-
         click.option('--log-file', '-l',
                      help="logfile to send runtime logs to."),
 
-        click.option('--skip-pull-image',
-                     is_flag=True,
-                     help="Specify whether CLI should skip pulling down the latest Docker image for Lambda runtime.",
-                     envvar="SAM_SKIP_PULL_IMAGE"),
+    ] + docker_click_options() + [
 
         click.option('--profile',
                      help="Specify which AWS credentials profile to use."),
diff --git a/samcli/commands/validate/validate.py b/samcli/commands/validate/validate.py
index 40080a208f..693658049b 100644
--- a/samcli/commands/validate/validate.py
+++ b/samcli/commands/validate/validate.py
@@ -10,7 +10,7 @@
 
 from samcli.commands.exceptions import UserException
 from samcli.cli.main import pass_context, common_options as cli_framework_options, aws_creds_options
-from samcli.commands.local.cli_common.options import template_common_option as template_option
+from samcli.commands._utils.options import template_option_without_build
 from samcli.commands.local.cli_common.user_exceptions import InvalidSamTemplateException, SamTemplateNotFoundException
 from samcli.yamlhelper import yaml_parse
 from .lib.exceptions import InvalidSamDocumentException
@@ -19,7 +19,7 @@
 
 @click.command("validate",
                short_help="Validate an AWS SAM template.")
-@template_option
+@template_option_without_build
 @aws_creds_options
 @cli_framework_options
 @pass_context
diff --git a/samcli/lib/build/__init__.py b/samcli/lib/build/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/samcli/lib/build/app_builder.py b/samcli/lib/build/app_builder.py
new file mode 100644
index 0000000000..d097b0cded
--- /dev/null
+++ b/samcli/lib/build/app_builder.py
@@ -0,0 +1,305 @@
+"""
+Builds the application
+"""
+
+import os
+import io
+import json
+import logging
+from collections import namedtuple
+
+try:
+    import pathlib
+except ImportError:
+    import pathlib2 as pathlib
+
+import docker
+
+import samcli.lib.utils.osutils as osutils
+from samcli.local.docker.lambda_build_container import LambdaBuildContainer
+from aws_lambda_builders.builder import LambdaBuilder
+from aws_lambda_builders.exceptions import LambdaBuilderError
+from aws_lambda_builders import RPC_PROTOCOL_VERSION as lambda_builders_protocol_version
+
+
+LOG = logging.getLogger(__name__)
+
+
+class UnsupportedRuntimeException(Exception):
+    pass
+
+
+class UnsupportedBuilderLibraryVersionError(Exception):
+
+    def __init__(self, container_name, error_msg):
+        msg = "You are running an outdated version of Docker container '{container_name}' that is not compatible with" \
+              "this version of SAM CLI. Please upgrade to continue to continue with build. Reason: '{error_msg}'"
+        Exception.__init__(self, msg.format(container_name=container_name, error_msg=error_msg))
+
+
+class BuildError(Exception):
+    pass
+
+
+def _get_workflow_config(runtime):
+
+    config = namedtuple('Capability', ["language", "dependency_manager", "application_framework", "manifest_name"])
+
+    if runtime.startswith("python"):
+        return config(
+            language="python",
+            dependency_manager="pip",
+            application_framework=None,
+            manifest_name="requirements.txt")
+    else:
+        raise UnsupportedRuntimeException("'{}' runtime is not supported".format(runtime))
+
+
+class ApplicationBuilder(object):
+    """
+    Class to build an entire application. Currently, this class builds Lambda functions only, but there is nothing that
+    is stopping this class from supporting other resource types. Building in context of Lambda functions refer to
+    converting source code into artifacts that can be run on AWS Lambda
+    """
+
+    def __init__(self,
+                 function_provider,
+                 build_dir,
+                 base_dir,
+                 manifest_path_override=None,
+                 container_manager=None,
+                 parallel=False):
+        """
+        Initialize the class
+
+        Parameters
+        ----------
+        function_provider : samcli.commands.local.lib.sam_function_provider.SamFunctionProvider
+            Provider that can vend out functions available in the SAM template
+
+        build_dir : str
+            Path to the directory where we will be storing built artifacts
+
+        base_dir : str
+            Path to a folder. Use this folder as the root to resolve relative source code paths against
+
+        container_manager : samcli.local.docker.manager.ContainerManager
+            Optional. If provided, we will attempt to build inside a Docker Container
+
+        parallel : bool
+            Optional. Set to True to build each function in parallel to improve performance
+        """
+        self._function_provider = function_provider
+        self._build_dir = build_dir
+        self._base_dir = base_dir
+        self._manifest_path_override = manifest_path_override
+
+        self._container_manager = container_manager
+        self._parallel = parallel
+
+    def build(self):
+        """
+        Build the entire application
+
+        Returns
+        -------
+        dict
+            Returns the path to where each resource was built as a map of resource's LogicalId to the path string
+        """
+
+        result = {}
+
+        for lambda_function in self._function_provider.get_all():
+
+            LOG.info("Building resource '%s'", lambda_function.name)
+            result[lambda_function.name] = self._build_function(lambda_function.name,
+                                                                lambda_function.codeuri,
+                                                                lambda_function.runtime)
+
+        return result
+
+    def update_template(self, template_dict, target_template_path, built_artifacts):
+        """
+        Given the path to built artifacts, update the template to point appropriate resource CodeUris to the artifacts
+        folder
+
+        Parameters
+        ----------
+        template_dict
+        target_template_path : str
+            Path where the template file will be written to
+
+        built_artifacts : dict
+            Map of LogicalId of a resource to the path where the the built artifacts for this resource lives
+
+        Returns
+        -------
+        dict
+            Updated template
+        """
+
+        target_dir = os.path.dirname(target_template_path)
+
+        for logical_id, resource in template_dict.get("Resources", {}).items():
+
+            if logical_id not in built_artifacts:
+                # this resource was not built. So skip it
+                continue
+
+            # Artifacts are written relative to the output template because it makes the template portability
+            #   Ex: A CI/CD pipeline build stage could zip the output folder and pass to a
+            #   package stage running on a different machine
+            artifact_relative_path = os.path.relpath(built_artifacts[logical_id], target_dir)
+
+            resource_type = resource.get("Type")
+            properties = resource.setdefault("Properties", {})
+            if resource_type == "AWS::Serverless::Function":
+                properties["CodeUri"] = artifact_relative_path
+
+            if resource_type == "AWS::Lambda::Function":
+                properties["Code"] = artifact_relative_path
+
+        return template_dict
+
+    def _build_function(self, function_name, codeuri, runtime):
+
+        config = _get_workflow_config(runtime)
+
+        # Create the arguments to pass to the builder
+
+        # Code is always relative to the given base directory.
+        code_dir = str(pathlib.Path(self._base_dir, codeuri).resolve())
+
+        # artifacts directory will be created by the builder
+        artifacts_dir = str(pathlib.Path(self._build_dir, function_name))
+
+        with osutils.mkdir_temp() as scratch_dir:
+            manifest_path = self._manifest_path_override or os.path.join(code_dir, config.manifest_name)
+
+            # By default prefer to build in-process for speed
+            build_method = self._build_function_in_process
+            if self._container_manager:
+                build_method = self._build_function_on_container
+
+            return build_method(config,
+                                code_dir,
+                                artifacts_dir,
+                                scratch_dir,
+                                manifest_path,
+                                runtime)
+
+    def _build_function_in_process(self,
+                                   config,
+                                   source_dir,
+                                   artifacts_dir,
+                                   scratch_dir,
+                                   manifest_path,
+                                   runtime):
+
+        builder = LambdaBuilder(language=config.language,
+                                dependency_manager=config.dependency_manager,
+                                application_framework=config.application_framework)
+
+        try:
+            builder.build(source_dir,
+                          artifacts_dir,
+                          scratch_dir,
+                          manifest_path,
+                          runtime=runtime)
+        except LambdaBuilderError as ex:
+            raise BuildError(str(ex))
+
+        return artifacts_dir
+
+    def _build_function_on_container(self,  # pylint: disable=too-many-locals
+                                     config,
+                                     source_dir,
+                                     artifacts_dir,
+                                     scratch_dir,
+                                     manifest_path,
+                                     runtime):
+
+        # If we are printing debug logs in SAM CLI, the builder library should also print debug logs
+        log_level = LOG.getEffectiveLevel()
+
+        container = LambdaBuildContainer(lambda_builders_protocol_version,
+                                         config.language,
+                                         config.dependency_manager,
+                                         config.application_framework,
+                                         source_dir,
+                                         manifest_path,
+                                         runtime,
+                                         log_level=log_level,
+                                         optimizations=None,
+                                         options=None)
+
+        try:
+            self._container_manager.run(container)
+        except docker.errors.APIError as ex:
+            if "executable file not found in $PATH" in str(ex):
+                raise UnsupportedBuilderLibraryVersionError(container.image,
+                                                            "{} executable not found in container"
+                                                            .format(container.executable_name))
+
+        # Container's output provides status of whether the build succeeded or failed
+        # stdout contains the result of JSON-RPC call
+        stdout_stream = io.BytesIO()
+        # stderr contains logs printed by the builder. Stream it directly to terminal
+        stderr_stream = osutils.stderr()
+        container.wait_for_logs(stdout=stdout_stream, stderr=stderr_stream)
+
+        stdout_data = stdout_stream.getvalue().decode('utf-8')
+        LOG.debug("Build inside container returned response %s", stdout_data)
+
+        response = self._parse_builder_response(stdout_data, container.image)
+
+        # Request is successful. Now copy the artifacts back to the host
+        LOG.debug("Build inside container was successful. Copying artifacts from container to host")
+
+        # "/." is a Docker thing that instructions the copy command to download contents of the folder only
+        result_dir_in_container = response["result"]["artifacts_dir"] + "/."
+        container.copy(result_dir_in_container, artifacts_dir)
+
+        LOG.debug("Build inside container succeeded")
+        return artifacts_dir
+
+    @staticmethod
+    def _parse_builder_response(stdout_data, image_name):
+
+        try:
+            response = json.loads(stdout_data)
+        except Exception:
+            # Invalid JSON is produced as an output only when the builder process crashed for some reason.
+            # Report this as a crash
+            LOG.debug("Builder crashed")
+            raise
+
+        if "error" in response:
+            error = response.get("error", {})
+            err_code = error.get("code")
+            msg = error.get("message")
+
+            if 400 <= err_code < 500:
+                # Like HTTP 4xx - customer error
+                raise BuildError(msg)
+
+            if err_code == 505:
+                # Like HTTP 505 error code: Version of the protocol is not supported
+                # In this case, this error means that the Builder Library within the container is
+                # not compatible with the version of protocol expected SAM CLI installation supports.
+                # This can happen when customers have a newer container image or an older SAM CLI version.
+                # https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/505
+                raise UnsupportedBuilderLibraryVersionError(image_name, msg)
+
+            if err_code == -32601:
+                # Default JSON Rpc Code for Method Unavailable https://www.jsonrpc.org/specification
+                # This can happen if customers are using an incompatible version of builder library within the
+                # container
+                LOG.debug("Builder library does not support the supplied method")
+                raise UnsupportedBuilderLibraryVersionError(image_name, msg)
+
+            else:
+                LOG.debug("Builder crashed")
+                raise ValueError(msg)
+
+        return response
diff --git a/samcli/lib/utils/osutils.py b/samcli/lib/utils/osutils.py
new file mode 100644
index 0000000000..399a644fa5
--- /dev/null
+++ b/samcli/lib/utils/osutils.py
@@ -0,0 +1,82 @@
+"""
+Common OS utilities
+"""
+
+import sys
+import os
+import shutil
+import tempfile
+
+from contextlib import contextmanager
+
+
+@contextmanager
+def mkdir_temp(mode=0o755):
+    """
+    Context manager that makes a temporary directory and yields it name. Directory is deleted
+    after the context exits
+
+    Parameters
+    ----------
+    mode : octal
+        Permissions to apply to the directory
+
+    Returns
+    -------
+    str
+        Path to the directory
+
+    """
+
+    temp_dir = None
+    try:
+        temp_dir = tempfile.mkdtemp()
+        os.chmod(temp_dir, mode)
+
+        yield temp_dir
+
+    finally:
+        if temp_dir:
+            shutil.rmtree(temp_dir)
+
+
+def stdout():
+    """
+    Returns the stdout as a byte stream in a Py2/PY3 compatible manner
+
+    Returns
+    -------
+    io.BytesIO
+        Byte stream of Stdout
+    """
+
+    # We write all of the data to stdout with bytes, typically io.BytesIO. stdout in Python2
+    # accepts bytes but Python3 does not. This is due to a type change on the attribute. To keep
+    # this consistent, we leave Python2 the same and get the .buffer attribute on stdout in Python3
+    byte_stdout = sys.stdout
+
+    if sys.version_info.major > 2:
+        byte_stdout = sys.stdout.buffer  # pylint: disable=no-member
+
+    return byte_stdout
+
+
+def stderr():
+    """
+    Returns the stderr as a byte stream in a Py2/PY3 compatible manner
+
+    Returns
+    -------
+    io.BytesIO
+        Byte stream of stderr
+    """
+
+    # We write all of the data to stderr with bytes, typically io.BytesIO. stderr in Python2
+    # accepts bytes but Python3 does not. This is due to a type change on the attribute. To keep
+    # this consistent, we leave Python2 the same and get the .buffer attribute on stderr in Python3
+    byte_stderr = sys.stderr
+
+    if sys.version_info.major > 2:
+        byte_stderr = sys.stderr.buffer  # pylint: disable=no-member
+
+    return byte_stderr
diff --git a/samcli/local/docker/container.py b/samcli/local/docker/container.py
index 8968e92138..466a139d92 100644
--- a/samcli/local/docker/container.py
+++ b/samcli/local/docker/container.py
@@ -3,6 +3,9 @@
 """
 
 import logging
+import tarfile
+import tempfile
+
 import docker
 
 from samcli.local.docker.attach_api import attach
@@ -202,6 +205,25 @@ def wait_for_logs(self, stdout=None, stderr=None):
 
         self._write_container_output(logs_itr, stdout=stdout, stderr=stderr)
 
+    def copy(self, from_container_path, to_host_path):
+
+        if not self.is_created():
+            raise RuntimeError("Container does not exist. Cannot get logs for this container")
+
+        real_container = self.docker_client.containers.get(self.id)
+
+        LOG.debug("Copying from container: %s -> %s", from_container_path, to_host_path)
+        with tempfile.NamedTemporaryFile() as fp:
+            tar_stream, _ = real_container.get_archive(from_container_path)
+            for data in tar_stream:
+                fp.write(data)
+
+            # Seek the handle back to start of file for tarfile to use
+            fp.seek(0)
+
+            with tarfile.open(fileobj=fp, mode='r') as tar:
+                tar.extractall(path=to_host_path)
+
     @staticmethod
     def _write_container_output(output_itr, stdout=None, stderr=None):
         """
diff --git a/samcli/local/docker/lambda_build_container.py b/samcli/local/docker/lambda_build_container.py
new file mode 100644
index 0000000000..368c9b054d
--- /dev/null
+++ b/samcli/local/docker/lambda_build_container.py
@@ -0,0 +1,164 @@
+"""
+Represents Lambda Build Containers.
+"""
+
+import json
+import logging
+
+try:
+    import pathlib
+except ImportError:
+    import pathlib2 as pathlib
+
+from .container import Container
+
+LOG = logging.getLogger(__name__)
+
+
+class LambdaBuildContainer(Container):
+    """
+    Class to manage Build containers that are capable of building AWS Lambda functions.
+    This container mounts necessary folders, issues a command to the Lambda Builder CLI,
+    and if the build was successful, copies back artifacts to the host filesystem
+    """
+
+    _IMAGE_REPO_NAME = "lambci/lambda"
+    _BUILDERS_EXECUTABLE = "lambda-builders"
+
+    def __init__(self,  # pylint: disable=too-many-locals
+                 protocol_version,
+                 language,
+                 dependency_manager,
+                 application_framework,
+                 source_dir,
+                 manifest_path,
+                 runtime,
+                 optimizations=None,
+                 options=None,
+                 log_level=None):
+
+        abs_manifest_path = pathlib.Path(manifest_path).resolve()
+        manifest_file_name = abs_manifest_path.name
+        manifest_dir = str(abs_manifest_path.parent)
+
+        source_dir = str(pathlib.Path(source_dir).resolve())
+
+        container_dirs = LambdaBuildContainer._get_container_dirs(source_dir, manifest_dir)
+
+        request_json = self._make_request(protocol_version,
+                                          language,
+                                          dependency_manager,
+                                          application_framework,
+                                          container_dirs,
+                                          manifest_file_name,
+                                          runtime,
+                                          optimizations,
+                                          options)
+
+        image = LambdaBuildContainer._get_image(runtime)
+        entry = LambdaBuildContainer._get_entrypoint(request_json)
+        cmd = []
+
+        additional_volumes = {
+            # Manifest is mounted separately in order to support the case where manifest
+            # is outside of source directory
+            manifest_dir: {
+                "bind": container_dirs["manifest_dir"],
+                "mode": "ro"
+            }
+        }
+
+        env_vars = None
+        if log_level:
+            env_vars = {
+                "LAMBDA_BUILDERS_LOG_LEVEL": log_level
+            }
+
+        super(LambdaBuildContainer, self).__init__(
+            image,
+            cmd,
+            container_dirs["source_dir"],
+            source_dir,
+            additional_volumes=additional_volumes,
+            entrypoint=entry,
+            env_vars=env_vars)
+
+    @property
+    def executable_name(self):
+        return LambdaBuildContainer._BUILDERS_EXECUTABLE
+
+    @staticmethod
+    def _make_request(protocol_version,
+                      language,
+                      dependency_manager,
+                      application_framework,
+                      container_dirs,
+                      manifest_file_name,
+                      runtime,
+                      optimizations,
+                      options):
+
+        return json.dumps({
+            "jsonschema": "2.0",
+            "id": 1,
+            "method": "LambdaBuilder.build",
+            "params": {
+                "__protocol_version": protocol_version,
+                "capability": {
+                    "language": language,
+                    "dependency_manager": dependency_manager,
+                    "application_framework": application_framework
+                },
+                "source_dir": container_dirs["source_dir"],
+                "artifacts_dir": container_dirs["artifacts_dir"],
+                "scratch_dir": container_dirs["scratch_dir"],
+
+                # Path is always inside a Linux container. So '/' is valid
+                "manifest_path": "{}/{}".format(container_dirs["manifest_dir"], manifest_file_name),
+
+                "runtime": runtime,
+                "optimizations": optimizations,
+                "options": options,
+            }
+        })
+
+    @staticmethod
+    def _get_entrypoint(request_json):
+        return [LambdaBuildContainer._BUILDERS_EXECUTABLE, request_json]
+
+    @staticmethod
+    def _get_container_dirs(source_dir, manifest_dir):
+        """
+        Provides paths to directories within the container that is required by the builder
+
+        Parameters
+        ----------
+        source_dir : str
+            Path to the function source code
+
+        manifest_dir : str
+            Path to the directory containing manifest
+
+        Returns
+        -------
+        dict
+            Contains paths to source, artifacts, scratch & manifest directories
+        """
+        base = "/tmp/samcli"
+        result = {
+            "source_dir": "{}/source".format(base),
+            "artifacts_dir": "{}/artifacts".format(base),
+            "scratch_dir": "{}/scratch".format(base),
+            "manifest_dir": "{}/manifest".format(base)
+        }
+
+        if pathlib.PurePath(source_dir) == pathlib.PurePath(manifest_dir):
+            # It is possible that the manifest resides within the source. In that case, we won't mount the manifest
+            # directory separately.
+            result["manifest_dir"] = result["source_dir"]
+
+        return result
+
+    @staticmethod
+    def _get_image(runtime):
+        return "{}:build-{}".format(LambdaBuildContainer._IMAGE_REPO_NAME, runtime)
diff --git a/samcli/local/init/templates/cookiecutter-aws-sam-hello-python/{{cookiecutter.project_name}}/requirements.txt b/samcli/local/init/templates/cookiecutter-aws-sam-hello-python/{{cookiecutter.project_name}}/hello_world/requirements.txt
similarity index 100%
rename from samcli/local/init/templates/cookiecutter-aws-sam-hello-python/{{cookiecutter.project_name}}/requirements.txt
rename to samcli/local/init/templates/cookiecutter-aws-sam-hello-python/{{cookiecutter.project_name}}/hello_world/requirements.txt
diff --git a/samcli/local/init/templates/cookiecutter-aws-sam-hello-python/{{cookiecutter.project_name}}/template.yaml b/samcli/local/init/templates/cookiecutter-aws-sam-hello-python/{{cookiecutter.project_name}}/template.yaml
index e7a21a0e5f..0037ccc7ee 100644
--- a/samcli/local/init/templates/cookiecutter-aws-sam-hello-python/{{cookiecutter.project_name}}/template.yaml
+++ b/samcli/local/init/templates/cookiecutter-aws-sam-hello-python/{{cookiecutter.project_name}}/template.yaml
@@ -4,7 +4,7 @@ Description: >
     {{ cookiecutter.project_name }}
 
     Sample SAM Template for {{ cookiecutter.project_name }}
-    
+
 # More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst
 Globals:
     Function:
@@ -16,7 +16,7 @@ Resources:
     HelloWorldFunction:
         Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
         Properties:
-            CodeUri: hello_world/build/
+            CodeUri: hello_world/
             Handler: app.lambda_handler
             {%- if cookiecutter.runtime == 'python2.7' %}
             Runtime: python2.7
diff --git a/tests/integration/buildcmd/__init__.py b/tests/integration/buildcmd/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/integration/buildcmd/build_integ_base.py b/tests/integration/buildcmd/build_integ_base.py
new file mode 100644
index 0000000000..d1efc7c99d
--- /dev/null
+++ b/tests/integration/buildcmd/build_integ_base.py
@@ -0,0 +1,79 @@
+import os
+import shutil
+import tempfile
+
+try:
+    from pathlib import Path
+except ImportError:
+    from pathlib2 import Path
+
+from unittest import TestCase
+
+
+class BuildIntegBase(TestCase):
+
+    @classmethod
+    def setUpClass(cls):
+        cls.cmd = cls.base_command()
+
+        integration_dir = Path(__file__).resolve().parents[1]
+
+        # To invoke a function creaated by the build command, we need the built artifacts to be in a
+        # location that is shared in Docker. Most temp directories are not shared. Therefore we are
+        # using a scratch space within the test folder that is .gitignored. Contents of this folder
+        # is also deleted after every test run
+        cls.scratch_dir = str(Path(__file__).resolve().parent.joinpath("scratch"))
+
+        cls.test_data_path = str(Path(integration_dir, "testdata", "buildcmd"))
+        cls.template_path = str(Path(cls.test_data_path, "template.yaml"))
+
+    def setUp(self):
+
+        shutil.rmtree(self.scratch_dir, ignore_errors=True)
+        os.mkdir(self.scratch_dir)
+
+        self.working_dir = tempfile.mkdtemp(dir=self.scratch_dir)
+        self.custom_build_dir = tempfile.mkdtemp(dir=self.scratch_dir)
+
+        self.default_build_dir = Path(self.working_dir, ".aws-sam", "build")
+        self.built_template = self.default_build_dir.joinpath("template.yaml")
+
+    def tearDown(self):
+        self.custom_build_dir and shutil.rmtree(self.custom_build_dir)
+        self.working_dir and shutil.rmtree(self.working_dir)
+        self.scratch_dir and shutil.rmtree(self.scratch_dir)
+
+    @classmethod
+    def base_command(cls):
+        command = "sam"
+        if os.getenv("SAM_CLI_DEV"):
+            command = "samdev"
+
+        return command
+
+    def get_command_list(self, build_dir=None, base_dir=None, manifest_path=None, use_container=None,
+                         parameter_overrides=None):
+
+        command_list = [self.cmd, "build", "-t", self.template_path]
+
+        if parameter_overrides:
+            command_list += ["--parameter-overrides", self._make_parameter_override_arg(parameter_overrides)]
+
+        if build_dir:
+            command_list += ["-b", build_dir]
+
+        if base_dir:
+            command_list += ["-s", base_dir]
+
+        if manifest_path:
+            command_list += ["-m", manifest_path]
+
+        if use_container:
+            command_list += ["--use-container"]
+
+        return command_list
+
+    def _make_parameter_override_arg(self, overrides):
+        return " ".join([
+            "ParameterKey={},ParameterValue={}".format(key, value) for key, value in overrides.items()
+        ])
diff --git a/tests/integration/buildcmd/test_build_cmd.py b/tests/integration/buildcmd/test_build_cmd.py
new file mode 100644
index 0000000000..ee90d9b600
--- /dev/null
+++ b/tests/integration/buildcmd/test_build_cmd.py
@@ -0,0 +1,118 @@
+import sys
+import os
+import subprocess
+import json
+import logging
+
+from parameterized import parameterized
+
+from samcli.yamlhelper import yaml_parse
+from .build_integ_base import BuildIntegBase
+
+
+LOG = logging.getLogger(__name__)
+
+
+class TestBuildCommand_PythonFunctions(BuildIntegBase):
+
+    EXPECTED_FILES_GLOBAL_MANIFEST = set()
+    EXPECTED_FILES_PROJECT_MANIFEST = {'__init__.py', 'main.py', 'numpy', 'cryptography', "jinja2",
+                                       'requirements.txt'}
+
+    FUNCTION_LOGICAL_ID = "PythonFunction"
+
+    @parameterized.expand([
+        ("python2.7", False),
+        ("python3.6", False)
+
+        # FIXME: Turning off Container build tests until official Docker Container is published
+        # ("python2.7", "use_container"),
+        # ("python3.6", "use_container"),
+    ])
+    def test_with_default_requirements(self, runtime, use_container):
+
+        # Don't run test on wrong Python versions
+        py_version = self._get_python_version()
+        if py_version != runtime:
+            self.skipTest("Current Python version '{}' does not match Lambda runtime version '{}'".format(py_version,
+                                                                                                          runtime))
+
+        overrides = {"PythonRuntime": runtime}
+        cmdlist = self.get_command_list(use_container=use_container,
+                                        parameter_overrides=overrides)
+
+        LOG.info("Running Command: {}", cmdlist)
+        process = subprocess.Popen(cmdlist, cwd=self.working_dir)
+        process.wait()
+
+        self._verify_built_artifact(self.default_build_dir, self.FUNCTION_LOGICAL_ID,
+                                    self.EXPECTED_FILES_PROJECT_MANIFEST)
+
+        expected = {
+            "pi": "3.14",
+            "jinja": "Hello World"
+        }
+        self._verify_invoke_built_function(self.built_template,
+                                           self.FUNCTION_LOGICAL_ID,
+                                           self._make_parameter_override_arg(overrides),
+                                           expected)
+
+    def _verify_invoke_built_function(self, template_path, function_logical_id, overrides, expected_result):
+        LOG.info("Invoking built function '{}'", function_logical_id)
+
+        cmdlist = [self.cmd, "local", "invoke", function_logical_id, "-t", str(template_path), "--no-event",
+                   "--parameter-overrides", overrides]
+
+        process = subprocess.Popen(cmdlist, stdout=subprocess.PIPE)
+        process.wait()
+
+        process_stdout = b"".join(process.stdout.readlines()).strip().decode('utf-8')
+        print(process_stdout)
+        self.assertEquals(json.loads(process_stdout), expected_result)
+
+    def _verify_built_artifact(self, build_dir, function_logical_id, expected_files):
+
+        self.assertTrue(build_dir.exists(), "Build directory should be created")
+
+        build_dir_files = os.listdir(str(build_dir))
+        self.assertIn("template.yaml", build_dir_files)
+        self.assertIn(function_logical_id, build_dir_files)
+
+        template_path = build_dir.joinpath("template.yaml")
+        resource_artifact_dir = build_dir.joinpath(function_logical_id)
+
+        # Make sure the template has correct CodeUri for resource
+        self._verify_resource_property(str(template_path),
+                                       function_logical_id,
+                                       "CodeUri",
+                                       function_logical_id)
+
+        all_artifacts = set(os.listdir(str(resource_artifact_dir)))
+        print(all_artifacts)
+        actual_files = all_artifacts.intersection(expected_files)
+        self.assertEquals(actual_files, expected_files)
+
+    def _verify_resource_property(self, template_path, logical_id, property, expected_value):
+
+        with open(template_path, 'r') as fp:
+            template_dict = yaml_parse(fp.read())
+            self.assertEquals(expected_value, template_dict["Resources"][logical_id]["Properties"][property])
+
+    def _get_python_version(self):
+        return "python{}.{}".format(sys.version_info.major, sys.version_info.minor)
+
+
+class TestBuildCommand_ErrorCases(BuildIntegBase):
+
+    def test_unsupported_runtime(self):
+        overrides = {"PythonRuntime": "unsupportedpython"}
+        cmdlist = self.get_command_list(parameter_overrides=overrides)
+
+        LOG.info("Running Command: {}", cmdlist)
+        process = subprocess.Popen(cmdlist, cwd=self.working_dir, stdout=subprocess.PIPE)
+        process.wait()
+
+        process_stdout = b"".join(process.stdout.readlines()).strip().decode('utf-8')
+        self.assertEquals(1, process.returncode)
+
+        self.assertIn("Build Failed", process_stdout)
diff --git a/tests/integration/testdata/buildcmd/Python/__init__.py b/tests/integration/testdata/buildcmd/Python/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/integration/testdata/buildcmd/Python/main.py b/tests/integration/testdata/buildcmd/Python/main.py
new file mode 100644
index 0000000000..3800cf819a
--- /dev/null
+++ b/tests/integration/testdata/buildcmd/Python/main.py
@@ -0,0 +1,16 @@
+import numpy
+from cryptography.fernet import Fernet
+from jinja2 import Template
+
+
+def handler(event, context):
+
+    # Try using some of the modules to make sure they work & don't crash the process
+    print(Fernet.generate_key())
+
+    template = Template('Hello {{ name }}')
+
+    return {
+        "pi": "{0:.2f}".format(numpy.pi),
+        "jinja": template.render(name='World')
+    }
diff --git a/tests/integration/testdata/buildcmd/Python/requirements.txt b/tests/integration/testdata/buildcmd/Python/requirements.txt
new file mode 100644
index 0000000000..ffa4678cc5
--- /dev/null
+++ b/tests/integration/testdata/buildcmd/Python/requirements.txt
@@ -0,0 +1,5 @@
+# These are some hard packages to build. Using them here helps us verify that building works on various platforms
+
+numpy~=1.15
+cryptography~=2.4
+Jinja2~=2.10
diff --git a/tests/integration/testdata/buildcmd/template.yaml b/tests/integration/testdata/buildcmd/template.yaml
new file mode 100644
index 0000000000..716574ceb2
--- /dev/null
+++ b/tests/integration/testdata/buildcmd/template.yaml
@@ -0,0 +1,18 @@
+AWSTemplateFormatVersion : '2010-09-09'
+Transform: AWS::Serverless-2016-10-31
+
+Parameteres:
+  PythonRuntime:
+    Type: String
+
+Resources:
+
+  PythonFunction:
+    Type: AWS::Serverless::Function
+    Properties:
+      Handler: main.handler
+      Runtime: !Ref PythonRuntime
+      CodeUri: Python
+      Timeout: 600
+
+
diff --git a/tests/unit/commands/_utils/__init__.py b/tests/unit/commands/_utils/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/unit/commands/local/cli_common/test_options.py b/tests/unit/commands/_utils/test_options.py
similarity index 54%
rename from tests/unit/commands/local/cli_common/test_options.py
rename to tests/unit/commands/_utils/test_options.py
index e15ef13218..83e533a71f 100644
--- a/tests/unit/commands/local/cli_common/test_options.py
+++ b/tests/unit/commands/_utils/test_options.py
@@ -1,12 +1,12 @@
 """
-Test the common CLI options for invoke
+Test the common CLI options
 """
 
 import os
 
 from unittest import TestCase
 from mock import patch
-from samcli.commands.local.cli_common.options import get_or_default_template_file_name, _TEMPLATE_OPTION_DEFAULT_VALUE
+from samcli.commands._utils.options import get_or_default_template_file_name, _TEMPLATE_OPTION_DEFAULT_VALUE
 
 
 class TestGetOrDefaultTemplateFileName(TestCase):
@@ -15,27 +15,39 @@ def test_must_return_abspath_of_user_provided_value(self):
         filename = "foo.txt"
         expected = os.path.abspath(filename)
 
-        result = get_or_default_template_file_name(None, None, filename)
+        result = get_or_default_template_file_name(None, None, filename, include_build=False)
         self.assertEquals(result, expected)
 
-    @patch("samcli.commands.local.cli_common.options.os")
+    @patch("samcli.commands._utils.options.os")
     def test_must_return_yml_extension(self, os_mock):
         expected = "template.yml"
 
         os_mock.path.exists.return_value = False  # Fake .yaml file to not exist.
         os_mock.path.abspath.return_value = "absPath"
 
-        result = get_or_default_template_file_name(None, None, _TEMPLATE_OPTION_DEFAULT_VALUE)
+        result = get_or_default_template_file_name(None, None, _TEMPLATE_OPTION_DEFAULT_VALUE, include_build=False)
         self.assertEquals(result, "absPath")
         os_mock.path.abspath.assert_called_with(expected)
 
-    @patch("samcli.commands.local.cli_common.options.os")
+    @patch("samcli.commands._utils.options.os")
     def test_must_return_yaml_extension(self, os_mock):
         expected = "template.yaml"
 
         os_mock.path.exists.return_value = True
         os_mock.path.abspath.return_value = "absPath"
 
-        result = get_or_default_template_file_name(None, None, _TEMPLATE_OPTION_DEFAULT_VALUE)
+        result = get_or_default_template_file_name(None, None, _TEMPLATE_OPTION_DEFAULT_VALUE, include_build=False)
+        self.assertEquals(result, "absPath")
+        os_mock.path.abspath.assert_called_with(expected)
+
+    @patch("samcli.commands._utils.options.os")
+    def test_must_return_built_template(self, os_mock):
+        expected = os.path.join(".aws-sam", "build", "template.yaml")
+
+        os_mock.path.exists.return_value = True
+        os_mock.path.join = os.path.join  # Use the real method
+        os_mock.path.abspath.return_value = "absPath"
+
+        result = get_or_default_template_file_name(None, None, _TEMPLATE_OPTION_DEFAULT_VALUE, include_build=True)
         self.assertEquals(result, "absPath")
         os_mock.path.abspath.assert_called_with(expected)
diff --git a/tests/unit/commands/_utils/test_template.py b/tests/unit/commands/_utils/test_template.py
new file mode 100644
index 0000000000..305bf468cc
--- /dev/null
+++ b/tests/unit/commands/_utils/test_template.py
@@ -0,0 +1,63 @@
+
+import yaml
+
+from unittest import TestCase
+from mock import patch, mock_open
+from parameterized import parameterized, param
+
+from samcli.commands._utils.template import get_template_data
+
+
+class TestInvokeContext_get_template_data(TestCase):
+
+    def test_must_raise_if_file_does_not_exist(self):
+        filename = "filename"
+
+        with self.assertRaises(ValueError) as exception_ctx:
+            get_template_data(filename)
+
+        ex = exception_ctx.exception
+        self.assertEquals(str(ex), "Template file not found at {}".format(filename))
+
+    @patch("samcli.commands._utils.template.yaml_parse")
+    @patch("samcli.commands._utils.template.pathlib")
+    def test_must_read_file_and_parse(self, pathlib_mock, yaml_parse_mock):
+        filename = "filename"
+        file_data = "contents of the file"
+        parse_result = "parse result"
+
+        pathlib_mock.Path.return_value.exists.return_value = True  # Fake that the file exists
+
+        m = mock_open(read_data=file_data)
+        yaml_parse_mock.return_value = parse_result
+
+        with patch("samcli.commands._utils.template.open", m):
+            result = get_template_data(filename)
+
+            self.assertEquals(result, parse_result)
+
+        m.assert_called_with(filename, 'r')
+        yaml_parse_mock.assert_called_with(file_data)
+
+    @parameterized.expand([
+        param(ValueError()),
+        param(yaml.YAMLError())
+    ])
+    @patch("samcli.commands._utils.template.yaml_parse")
+    @patch("samcli.commands._utils.template.pathlib")
+    def test_must_raise_on_parse_errors(self, exception, pathlib_mock, yaml_parse_mock):
+        filename = "filename"
+        file_data = "contents of the file"
+
+        pathlib_mock.Path.return_value.exists.return_value = True  # Fake that the file exists
+
+        m = mock_open(read_data=file_data)
+        yaml_parse_mock.side_effect = exception
+
+        with patch("samcli.commands._utils.template.open", m):
+
+            with self.assertRaises(ValueError) as ex_ctx:
+                get_template_data(filename)
+
+            actual_exception = ex_ctx.exception
+            self.assertTrue(str(actual_exception).startswith("Failed to parse template: "))
diff --git a/tests/unit/commands/buildcmd/__init__.py b/tests/unit/commands/buildcmd/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/unit/commands/buildcmd/test_build_context.py b/tests/unit/commands/buildcmd/test_build_context.py
new file mode 100644
index 0000000000..193b899bb6
--- /dev/null
+++ b/tests/unit/commands/buildcmd/test_build_context.py
@@ -0,0 +1,54 @@
+import os
+
+from unittest import TestCase
+from mock import patch, Mock
+
+from samcli.commands.build.build_context import BuildContext
+
+
+class TestBuildContext__enter__(TestCase):
+
+    @patch("samcli.commands.build.build_context.get_template_data")
+    @patch("samcli.commands.build.build_context.SamFunctionProvider")
+    @patch("samcli.commands.build.build_context.pathlib")
+    @patch("samcli.commands.build.build_context.ContainerManager")
+    def test_must_setup_context(self, ContainerManagerMock, pathlib_mock, SamFunctionProviderMock,
+                                get_template_data_mock):
+
+        template_dict = get_template_data_mock.return_value = "template dict"
+        funcprovider = SamFunctionProviderMock.return_value = "funcprovider"
+        base_dir = pathlib_mock.Path.return_value.resolve.return_value.parent = "basedir"
+        container_mgr_mock = ContainerManagerMock.return_value = Mock()
+
+        context = BuildContext("template_file",
+                               None,  # No base dir is provided
+                               "build_dir",
+                               manifest_path="manifest_path",
+                               clean=True,
+                               use_container=True,
+                               docker_network="network",
+                               parameter_overrides="overrides",
+                               skip_pull_image=True)
+        setup_build_dir_mock = Mock()
+        build_dir_result = setup_build_dir_mock.return_value = "my/new/build/dir"
+        context._setup_build_dir = setup_build_dir_mock
+
+        # call the enter method
+        result = context.__enter__()
+
+        self.assertEquals(result, context)  # __enter__ must return self
+        self.assertEquals(context.template_dict, template_dict)
+        self.assertEquals(context.function_provider, funcprovider)
+        self.assertEquals(context.base_dir, base_dir)
+        self.assertEquals(context.container_manager, container_mgr_mock)
+        self.assertEquals(context.build_dir, build_dir_result)
+        self.assertEquals(context.use_container, True)
+        self.assertEquals(context.output_template_path, os.path.join(build_dir_result, "template.yaml"))
+        self.assertEquals(context.manifest_path_override, os.path.abspath("manifest_path"))
+
+        get_template_data_mock.assert_called_once_with("template_file")
+        SamFunctionProviderMock.assert_called_once_with(template_dict, "overrides")
+        pathlib_mock.Path.assert_called_once_with("template_file")
+        setup_build_dir_mock.assert_called_with("build_dir", True)
+        ContainerManagerMock.assert_called_once_with(docker_network_id="network",
+                                                     skip_pull_image=True)
diff --git a/tests/unit/commands/buildcmd/test_command.py b/tests/unit/commands/buildcmd/test_command.py
new file mode 100644
index 0000000000..df8bf83a55
--- /dev/null
+++ b/tests/unit/commands/buildcmd/test_command.py
@@ -0,0 +1,66 @@
+
+from unittest import TestCase
+from mock import Mock, patch, mock_open
+from parameterized import parameterized
+
+from samcli.commands.build.command import do_cli
+from samcli.commands.exceptions import UserException
+from samcli.lib.build.app_builder import UnsupportedRuntimeException, BuildError, UnsupportedBuilderLibraryVersionError
+
+
+class TestDoCli(TestCase):
+
+    @patch("samcli.commands.build.command.BuildContext")
+    @patch("samcli.commands.build.command.ApplicationBuilder")
+    @patch("samcli.commands.build.command.yaml_dump")
+    @patch("samcli.commands.build.command.os")
+    def test_must_succeed_build(self, os_mock, yaml_dump_mock, ApplicationBuilderMock, BuildContextMock):
+
+        ctx_mock = Mock()
+        BuildContextMock.return_value.__enter__ = Mock()
+        BuildContextMock.return_value.__enter__.return_value = ctx_mock
+        builder_mock = ApplicationBuilderMock.return_value = Mock()
+        artifacts = builder_mock.build.return_value = "artifacts"
+        modified_template = builder_mock.update_template.return_value = "modified template"
+        dumped_yaml = yaml_dump_mock.return_value = "dumped yaml"
+        m = mock_open()
+
+        with patch("samcli.commands.build.command.open", m):
+            do_cli("template", "base_dir", "build_dir", "clean", "use_container",
+                   "manifest_path", "docker_network", "skip_pull", "parameter_overrides")
+
+        ApplicationBuilderMock.assert_called_once_with(ctx_mock.function_provider,
+                                                       ctx_mock.build_dir,
+                                                       ctx_mock.base_dir,
+                                                       manifest_path_override=ctx_mock.manifest_path_override,
+                                                       container_manager=ctx_mock.container_manager)
+        builder_mock.build.assert_called_once()
+        builder_mock.update_template.assert_called_once_with(ctx_mock.template_dict,
+                                                             ctx_mock.output_template_path,
+                                                             artifacts)
+
+        yaml_dump_mock.assert_called_with(modified_template)
+        m.assert_called_with(ctx_mock.output_template_path, 'w')
+        m.return_value.write.assert_called_with(dumped_yaml)
+
+    @parameterized.expand([
+        (UnsupportedRuntimeException(), ),
+        (BuildError(), ),
+        (UnsupportedBuilderLibraryVersionError(container_name="name", error_msg="msg"), )
+    ])
+    @patch("samcli.commands.build.command.BuildContext")
+    @patch("samcli.commands.build.command.ApplicationBuilder")
+    def test_must_catch_known_exceptions(self, exception, ApplicationBuilderMock, BuildContextMock):
+
+        ctx_mock = Mock()
+        BuildContextMock.return_value.__enter__ = Mock()
+        BuildContextMock.return_value.__enter__.return_value = ctx_mock
+        builder_mock = ApplicationBuilderMock.return_value = Mock()
+
+        builder_mock.build.side_effect = exception
+
+        with self.assertRaises(UserException) as ctx:
+            do_cli("template", "base_dir", "build_dir", "clean", "use_container",
+                   "manifest_path", "docker_network", "skip_pull", "parameteroverrides")
+
+        self.assertEquals(str(ctx.exception), str(exception))
diff --git a/tests/unit/commands/local/cli_common/test_invoke_context.py b/tests/unit/commands/local/cli_common/test_invoke_context.py
index ba596377f3..9e842dc8ad 100644
--- a/tests/unit/commands/local/cli_common/test_invoke_context.py
+++ b/tests/unit/commands/local/cli_common/test_invoke_context.py
@@ -4,7 +4,6 @@
 import errno
 import os
 import sys
-import yaml
 
 import docker
 import requests
@@ -272,61 +271,6 @@ def test_must_return_docker_volume_dir(self):
         self.assertEquals(result, "basedir")
 
 
-class TestInvokeContext_get_template_data(TestCase):
-
-    def test_must_raise_if_file_does_not_exist(self):
-        filename = "filename"
-
-        with self.assertRaises(InvokeContextException) as exception_ctx:
-            InvokeContext._get_template_data(filename)
-
-        ex = exception_ctx.exception
-        self.assertEquals(str(ex), "Template file not found at {}".format(filename))
-
-    @patch("samcli.commands.local.cli_common.invoke_context.yaml_parse")
-    @patch("samcli.commands.local.cli_common.invoke_context.os")
-    def test_must_read_file_and_parse(self, os_mock, yaml_parse_mock):
-        filename = "filename"
-        file_data = "contents of the file"
-        parse_result = "parse result"
-
-        os_mock.patch.exists.return_value = True  # Fake that the file exists
-
-        m = mock_open(read_data=file_data)
-        yaml_parse_mock.return_value = parse_result
-
-        with patch("samcli.commands.local.cli_common.invoke_context.open", m):
-            result = InvokeContext._get_template_data(filename)
-
-            self.assertEquals(result, parse_result)
-
-        m.assert_called_with(filename, 'r')
-        yaml_parse_mock.assert_called_with(file_data)
-
-    @parameterized.expand([
-        param(ValueError()),
-        param(yaml.YAMLError())
-    ])
-    @patch("samcli.commands.local.cli_common.invoke_context.yaml_parse")
-    @patch("samcli.commands.local.cli_common.invoke_context.os")
-    def test_must_raise_on_parse_errors(self, exception, os_mock, yaml_parse_mock):
-        filename = "filename"
-        file_data = "contents of the file"
-
-        os_mock.patch.exists.return_value = True  # Fake that the file exists
-
-        m = mock_open(read_data=file_data)
-        yaml_parse_mock.side_effect = exception
-
-        with patch("samcli.commands.local.cli_common.invoke_context.open", m):
-
-            with self.assertRaises(InvokeContextException) as ex_ctx:
-                InvokeContext._get_template_data(filename)
-
-            actual_exception = ex_ctx.exception
-            self.assertTrue(str(actual_exception).startswith("Failed to parse template: "))
-
-
 class TestInvokeContext_get_env_vars_value(TestCase):
 
     def test_must_return_if_no_file(self):
diff --git a/tests/unit/lib/build_module/__init__.py b/tests/unit/lib/build_module/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/unit/lib/build_module/test_app_builder.py b/tests/unit/lib/build_module/test_app_builder.py
new file mode 100644
index 0000000000..207c151c61
--- /dev/null
+++ b/tests/unit/lib/build_module/test_app_builder.py
@@ -0,0 +1,390 @@
+
+import os
+import docker
+import json
+
+from unittest import TestCase
+from mock import Mock, call, patch
+from parameterized import parameterized
+
+from samcli.lib.build.app_builder import ApplicationBuilder, _get_workflow_config,\
+    UnsupportedBuilderLibraryVersionError, UnsupportedRuntimeException, BuildError, \
+    LambdaBuilderError
+
+
+class Test_get_workflow_config(TestCase):
+
+    @parameterized.expand([
+        ("python2.7", ),
+        ("python3.6", )
+    ])
+    def test_must_work_for_python(self, runtime):
+
+        result = _get_workflow_config(runtime)
+        self.assertEquals(result.language, "python")
+        self.assertEquals(result.dependency_manager, "pip")
+        self.assertEquals(result.application_framework, None)
+        self.assertEquals(result.manifest_name, "requirements.txt")
+
+    def test_must_raise_for_unsupported_runtimes(self):
+
+        runtime = "foobar"
+
+        with self.assertRaises(UnsupportedRuntimeException) as ctx:
+            _get_workflow_config(runtime)
+
+        self.assertEquals(str(ctx.exception),
+                          "'foobar' runtime is not supported")
+
+
+class TestApplicationBuilder_build(TestCase):
+
+    def setUp(self):
+        self.mock_func_provider = Mock()
+        self.builder = ApplicationBuilder(self.mock_func_provider,
+                                          "builddir",
+                                          "basedir")
+
+    def test_must_iterate_on_functions(self):
+        func1 = Mock()
+        func2 = Mock()
+        build_function_mock = Mock()
+
+        self.mock_func_provider.get_all.return_value = [func1, func2]
+        self.builder._build_function = build_function_mock
+
+        result = self.builder.build()
+
+        self.assertEquals(result, {
+            func1.name: build_function_mock.return_value,
+            func2.name: build_function_mock.return_value,
+        })
+
+        build_function_mock.assert_has_calls([
+            call(func1.name, func1.codeuri, func1.runtime),
+            call(func2.name, func2.codeuri, func2.runtime),
+        ], any_order=False)
+
+
+class TestApplicationBuilder_update_template(TestCase):
+
+    def setUp(self):
+        self.builder = ApplicationBuilder(Mock(),
+                                          "builddir",
+                                          "basedir")
+
+        self.template_dict = {
+            "Resources": {
+                "MyFunction1": {
+                    "Type": "AWS::Serverless::Function",
+                    "Properties": {
+                        "CodeUri": "oldvalue"
+                    }
+                },
+                "MyFunction2": {
+                    "Type": "AWS::Lambda::Function",
+                    "Properties": {
+                        "Code": "oldvalue"
+                    }
+                },
+                "OtherResource": {
+                    "Type": "AWS::Lambda::Version",
+                    "Properties": {
+                        "CodeUri": "something"
+                    }
+                }
+            }
+        }
+
+    def test_must_write_relative_build_artifacts_path(self):
+        target_template_path = "/path/to/tempate.txt"
+        built_artifacts = {
+            "MyFunction1": "/path/to/build/MyFunction1",
+            "MyFunction2": "/path/to/build/MyFunction2"
+        }
+
+        expected_result = {
+            "Resources": {
+                "MyFunction1": {
+                    "Type": "AWS::Serverless::Function",
+                    "Properties": {
+                        "CodeUri": os.path.join("build", "MyFunction1")
+                    }
+                },
+                "MyFunction2": {
+                    "Type": "AWS::Lambda::Function",
+                    "Properties": {
+                        "Code": os.path.join("build", "MyFunction2")
+                    }
+                },
+                "OtherResource": {
+                    "Type": "AWS::Lambda::Version",
+                    "Properties": {
+                        "CodeUri": "something"
+                    }
+                }
+            }
+        }
+
+        actual = self.builder.update_template(self.template_dict, target_template_path, built_artifacts)
+        self.assertEquals(actual, expected_result)
+
+    def test_must_skip_if_no_artifacts(self):
+
+        built_artifacts = {}
+        actual = self.builder.update_template(self.template_dict, "/foo/bar/template.txt", built_artifacts)
+
+        self.assertEquals(actual, self.template_dict)
+
+
+class TestApplicationBuilder_build_function(TestCase):
+
+    def setUp(self):
+        self.builder = ApplicationBuilder(Mock(),
+                                          "/build/dir",
+                                          "/base/dir")
+
+    @patch("samcli.lib.build.app_builder._get_workflow_config")
+    @patch("samcli.lib.build.app_builder.osutils")
+    def test_must_build_in_process(self, osutils_mock, get_workflow_config_mock):
+        function_name = "function_name"
+        codeuri = "path/to/source"
+        runtime = "runtime"
+        scratch_dir = "scratch"
+        config_mock = get_workflow_config_mock.return_value = Mock()
+        config_mock.manifest_name = "manifest_name"
+
+        osutils_mock.mkdir_temp.return_value.__enter__ = Mock(return_value=scratch_dir)
+        osutils_mock.mkdir_temp.return_value.__exit__ = Mock()
+
+        self.builder._build_function_in_process = Mock()
+
+        code_dir = "/base/dir/path/to/source"
+        artifacts_dir = "/build/dir/function_name"
+        manifest_path = os.path.join(code_dir, config_mock.manifest_name)
+
+        self.builder._build_function(function_name, codeuri, runtime)
+
+        self.builder._build_function_in_process.assert_called_with(config_mock,
+                                                                   code_dir,
+                                                                   artifacts_dir,
+                                                                   scratch_dir,
+                                                                   manifest_path,
+                                                                   runtime)
+
+    @patch("samcli.lib.build.app_builder._get_workflow_config")
+    @patch("samcli.lib.build.app_builder.osutils")
+    def test_must_build_in_container(self, osutils_mock, get_workflow_config_mock):
+        function_name = "function_name"
+        codeuri = "path/to/source"
+        runtime = "runtime"
+        scratch_dir = "scratch"
+        config_mock = get_workflow_config_mock.return_value = Mock()
+        config_mock.manifest_name = "manifest_name"
+
+        osutils_mock.mkdir_temp.return_value.__enter__ = Mock(return_value=scratch_dir)
+        osutils_mock.mkdir_temp.return_value.__exit__ = Mock()
+
+        self.builder._build_function_on_container = Mock()
+
+        code_dir = "/base/dir/path/to/source"
+        artifacts_dir = "/build/dir/function_name"
+        manifest_path = os.path.join(code_dir, config_mock.manifest_name)
+
+        # Settting the container manager will make us use the container
+        self.builder._container_manager = Mock()
+        self.builder._build_function(function_name, codeuri, runtime)
+
+        self.builder._build_function_on_container.assert_called_with(config_mock,
+                                                                     code_dir,
+                                                                     artifacts_dir,
+                                                                     scratch_dir,
+                                                                     manifest_path,
+                                                                     runtime)
+
+
+class TestApplicationBuilder_build_function_in_process(TestCase):
+
+    def setUp(self):
+        self.builder = ApplicationBuilder(Mock(),
+                                          "/build/dir",
+                                          "/base/dir")
+
+    @patch("samcli.lib.build.app_builder.LambdaBuilder")
+    def test_must_use_lambda_builder(self, lambda_builder_mock):
+        config_mock = Mock()
+        builder_instance_mock = lambda_builder_mock.return_value = Mock()
+
+        result = self.builder._build_function_in_process(config_mock,
+                                                         "source_dir",
+                                                         "artifacts_dir",
+                                                         "scratch_dir",
+                                                         "manifest_path",
+                                                         "runtime")
+        self.assertEquals(result, "artifacts_dir")
+
+        lambda_builder_mock.assert_called_with(language=config_mock.language,
+                                               dependency_manager=config_mock.dependency_manager,
+                                               application_framework=config_mock.application_framework)
+
+        builder_instance_mock.build.assert_called_with("source_dir",
+                                                       "artifacts_dir",
+                                                       "scratch_dir",
+                                                       "manifest_path",
+                                                       runtime="runtime")
+
+    @patch("samcli.lib.build.app_builder.LambdaBuilder")
+    def test_must_raise_on_error(self, lambda_builder_mock):
+        config_mock = Mock()
+        builder_instance_mock = lambda_builder_mock.return_value = Mock()
+        builder_instance_mock.build.side_effect = LambdaBuilderError()
+
+        with self.assertRaises(BuildError):
+            self.builder._build_function_in_process(config_mock,
+                                                    "source_dir",
+                                                    "artifacts_dir",
+                                                    "scratch_dir",
+                                                    "manifest_path",
+                                                    "runtime")
+
+
+class TestApplicationBuilder_build_function_on_container(TestCase):
+
+    def setUp(self):
+        self.container_manager = Mock()
+        self.builder = ApplicationBuilder(Mock(),
+                                          "/build/dir",
+                                          "/base/dir",
+                                          container_manager=self.container_manager)
+        self.builder._parse_builder_response = Mock()
+
+    @patch("samcli.lib.build.app_builder.LambdaBuildContainer")
+    @patch("samcli.lib.build.app_builder.lambda_builders_protocol_version")
+    @patch("samcli.lib.build.app_builder.LOG")
+    @patch("samcli.lib.build.app_builder.osutils")
+    def test_must_build_in_container(self, osutils_mock, LOGMock, protocol_version_mock, LambdaBuildContainerMock):
+        config = Mock()
+        log_level = LOGMock.getEffectiveLevel.return_value = 'foo'
+        stdout_data = "container stdout response data"
+        response = {
+            "result": {
+                "artifacts_dir": "/some/dir"
+            }
+
+        }
+
+        def mock_wait_for_logs(stdout, stderr):
+            stdout.write(stdout_data.encode('utf-8'))
+
+        # Wire all mocks correctly
+        container_mock = LambdaBuildContainerMock.return_value = Mock()
+        container_mock.wait_for_logs = mock_wait_for_logs
+        self.builder._parse_builder_response.return_value = response
+
+        result = self.builder._build_function_on_container(config,
+                                                           "source_dir",
+                                                           "artifacts_dir",
+                                                           "scratch_dir",
+                                                           "manifest_path",
+                                                           "runtime")
+        self.assertEquals(result, "artifacts_dir")
+
+        LambdaBuildContainerMock.assert_called_once_with(protocol_version_mock,
+                                                         config.language,
+                                                         config.dependency_manager,
+                                                         config.application_framework,
+                                                         "source_dir",
+                                                         "manifest_path",
+                                                         "runtime",
+                                                         log_level=log_level,
+                                                         optimizations=None,
+                                                         options=None)
+
+        self.container_manager.run.assert_called_with(container_mock)
+        self.builder._parse_builder_response.assert_called_once_with(stdout_data, container_mock.image)
+        container_mock.copy.assert_called_with(response["result"]["artifacts_dir"] + "/.",
+                                               "artifacts_dir")
+
+    @patch("samcli.lib.build.app_builder.LambdaBuildContainer")
+    def test_must_raise_on_unsupported_container(self, LambdaBuildContainerMock):
+        config = Mock()
+
+        container_mock = LambdaBuildContainerMock.return_value = Mock()
+        container_mock.image = "image name"
+        container_mock.executable_name = "myexecutable"
+
+        self.container_manager.run.side_effect = docker.errors.APIError("Bad Request: 'lambda-builders' "
+                                                                        "executable file not found in $PATH")
+
+        with self.assertRaises(UnsupportedBuilderLibraryVersionError) as ctx:
+            self.builder._build_function_on_container(config,
+                                                      "source_dir",
+                                                      "artifacts_dir",
+                                                      "scratch_dir",
+                                                      "manifest_path",
+                                                      "runtime")
+
+        msg = "You are running an outdated version of Docker container 'image name' that is not compatible with" \
+              "this version of SAM CLI. Please upgrade to continue to continue with build. " \
+              "Reason: 'myexecutable executable not found in container'"
+
+        self.assertEquals(str(ctx.exception), msg)
+
+
+class TestApplicationBuilder_parse_builder_response(TestCase):
+
+    def setUp(self):
+        self.image_name = "name"
+        self.builder = ApplicationBuilder(Mock(),
+                                          "/build/dir",
+                                          "/base/dir")
+
+    def test_must_parse_json(self):
+        data = {"valid": "json"}
+
+        result = self.builder._parse_builder_response(json.dumps(data), self.image_name)
+        self.assertEquals(result, data)
+
+    def test_must_fail_on_invalid_json(self):
+        data = "{invalid: json}"
+
+        with self.assertRaises(ValueError):
+            self.builder._parse_builder_response(data, self.image_name)
+
+    def test_must_raise_on_user_error(self):
+        msg = "invalid params"
+        data = {"error": {"code": 488, "message": msg}}
+
+        with self.assertRaises(BuildError) as ctx:
+            self.builder._parse_builder_response(json.dumps(data), self.image_name)
+
+        self.assertEquals(str(ctx.exception), msg)
+
+    def test_must_raise_on_version_mismatch(self):
+        msg = "invalid params"
+        data = {"error": {"code": 505, "message": msg}}
+
+        with self.assertRaises(UnsupportedBuilderLibraryVersionError) as ctx:
+            self.builder._parse_builder_response(json.dumps(data), self.image_name)
+
+        expected = str(UnsupportedBuilderLibraryVersionError(self.image_name, msg))
+        self.assertEquals(str(ctx.exception), expected)
+
+    def test_must_raise_on_method_not_found(self):
+        msg = "invalid method"
+        data = {"error": {"code": -32601, "message": msg}}
+
+        with self.assertRaises(UnsupportedBuilderLibraryVersionError) as ctx:
+            self.builder._parse_builder_response(json.dumps(data), self.image_name)
+
+        expected = str(UnsupportedBuilderLibraryVersionError(self.image_name, msg))
+        self.assertEquals(str(ctx.exception), expected)
+
+    def test_must_raise_on_all_other_codes(self):
+        msg = "builder crashed"
+        data = {"error": {"code": 1, "message": msg}}
+
+        with self.assertRaises(ValueError) as ctx:
+            self.builder._parse_builder_response(json.dumps(data), self.image_name)
+
+        self.assertEquals(str(ctx.exception), msg)
diff --git a/tests/unit/lib/utils/test_osutils.py b/tests/unit/lib/utils/test_osutils.py
new file mode 100644
index 0000000000..10fb14132f
--- /dev/null
+++ b/tests/unit/lib/utils/test_osutils.py
@@ -0,0 +1,50 @@
+"""
+Tests OSUtils file
+"""
+
+import os
+import sys
+
+from unittest import TestCase
+import samcli.lib.utils.osutils as osutils
+
+
+class Test_mkdir_temp(TestCase):
+
+    def test_must_return_temp_dir(self):
+
+        with osutils.mkdir_temp() as tempdir:
+            self.assertTrue(os.path.exists(tempdir))
+
+    def test_must_delete_temp_dir_after_use(self):
+
+        dir_name = None
+        with osutils.mkdir_temp() as tempdir:
+            dir_name = tempdir
+            self.assertTrue(os.path.exists(tempdir))
+
+        self.assertFalse(os.path.exists(dir_name))
+
+
+class Test_stderr(TestCase):
+
+    def test_must_return_sys_stderr(self):
+
+        expected_stderr = sys.stderr
+
+        if sys.version_info.major > 2:
+            expected_stderr = sys.stderr.buffer
+
+        self.assertEquals(expected_stderr, osutils.stderr())
+
+
+class Test_stdout(TestCase):
+
+    def test_must_return_sys_stdout(self):
+
+        expected_stdout = sys.stdout
+
+        if sys.version_info.major > 2:
+            expected_stdout = sys.stdout.buffer
+
+        self.assertEquals(expected_stdout, osutils.stdout())
diff --git a/tests/unit/local/docker/test_container.py b/tests/unit/local/docker/test_container.py
index 333f24b215..1d7f3b25f7 100644
--- a/tests/unit/local/docker/test_container.py
+++ b/tests/unit/local/docker/test_container.py
@@ -564,3 +564,40 @@ def test_must_return_image_value(self):
         container = Container(image, "cmd", "dir", "dir")
 
         self.assertEquals(image, container.image)
+
+
+class TestContainer_copy(TestCase):
+
+    def setUp(self):
+        self.mock_client = Mock()
+        self.container = Container("image", "cmd", "dir", "dir", docker_client=self.mock_client)
+        self.container.id = "containerid"
+
+    @patch('samcli.local.docker.container.tempfile')
+    @patch('samcli.local.docker.container.tarfile')
+    def test_must_copy_files_from_container(self, tarfile_mock, tempfile_mock):
+        source = "source"
+        dest = "dest"
+
+        tar_stream = [1, 2, 3]
+        real_container_mock = self.mock_client.containers.get.return_value = Mock()
+        real_container_mock.get_archive.return_value = (tar_stream, "ignored")
+
+        tempfile_ctxmgr = tempfile_mock.NamedTemporaryFile.return_value = Mock()
+        fp_mock = Mock()
+        tempfile_ctxmgr.__enter__ = Mock(return_value=fp_mock)
+        tempfile_ctxmgr.__exit__ = Mock()
+
+        tarfile_ctxmgr = tarfile_mock.open.return_value = Mock()
+        tar_mock = Mock()
+        tarfile_ctxmgr.return_value.__enter__ = Mock(return_value=tar_mock)
+        tarfile_ctxmgr.return_value.__exit__ = Mock()
+
+        self.container.copy(source, dest)
+
+        # Make sure archive data is written to the file
+        fp_mock.write.assert_has_calls([call(x) for x in tar_stream], any_order=False)
+
+        # Make sure we open the tarfile right and extract to right location
+        tarfile_mock.open.assert_called_with(fileobj=fp_mock, mode='r')
+        tar_mock.extractall(path=dest)
diff --git a/tests/unit/local/docker/test_lambda_build_container.py b/tests/unit/local/docker/test_lambda_build_container.py
new file mode 100644
index 0000000000..6c993ab086
--- /dev/null
+++ b/tests/unit/local/docker/test_lambda_build_container.py
@@ -0,0 +1,157 @@
+"""
+Unit test for Lambda Build Container management
+"""
+
+import json
+
+from unittest import TestCase
+from mock import patch
+
+from samcli.local.docker.lambda_build_container import LambdaBuildContainer
+
+
+class TestLambdaBuildContainer_init(TestCase):
+
+    @patch.object(LambdaBuildContainer, "_make_request")
+    @patch.object(LambdaBuildContainer, "_get_image")
+    @patch.object(LambdaBuildContainer, "_get_entrypoint")
+    @patch.object(LambdaBuildContainer, "_get_container_dirs")
+    def test_must_init_class(self,
+                             get_container_dirs_mock,
+                             get_entrypoint_mock,
+                             get_image_mock,
+                             make_request_mock):
+
+        request = make_request_mock.return_value = "somerequest"
+        entry = get_entrypoint_mock.return_value = "entrypoint"
+        image = get_image_mock.return_value = "imagename"
+        container_dirs = get_container_dirs_mock.return_value = {
+            "source_dir": "/mysource",
+            "manifest_dir": "/mymanifest",
+            "artifacts_dir": "/myartifacts",
+            "scratch_dir": "/myscratch",
+        }
+
+        container = LambdaBuildContainer("protocol",
+                                         "language",
+                                         "dependency",
+                                         "application",
+                                         "/foo/source",
+                                         "/bar/manifest.txt",
+                                         "runtime",
+                                         optimizations="optimizations",
+                                         options="options",
+                                         log_level="log-level")
+
+        self.assertEquals(container.image, image)
+        self.assertEquals(container.executable_name, "lambda-builders")
+        self.assertEquals(container._entrypoint, entry)
+        self.assertEquals(container._cmd, [])
+        self.assertEquals(container._working_dir, container_dirs["source_dir"])
+        self.assertEquals(container._host_dir, "/foo/source")
+        self.assertEquals(container._env_vars, {"LAMBDA_BUILDERS_LOG_LEVEL": "log-level"})
+        self.assertEquals(container._additional_volumes, {
+            "/bar": {
+                "bind": container_dirs["manifest_dir"],
+                "mode": "ro"
+            }
+        })
+
+        self.assertEquals(container._exposed_ports, None)
+        self.assertEquals(container._memory_limit_mb, None)
+        self.assertEquals(container._network_id, None)
+        self.assertEquals(container._container_opts, None)
+
+        make_request_mock.assert_called_once()
+        get_entrypoint_mock.assert_called_once_with(request)
+        get_image_mock.assert_called_once_with("runtime")
+        get_container_dirs_mock.assert_called_once_with("/foo/source", "/bar")
+
+
+class TestLambdaBuildContainer_make_request(TestCase):
+
+    def test_must_make_request_object_string(self):
+
+        container_dirs = {
+            "source_dir": "source_dir",
+            "artifacts_dir": "artifacts_dir",
+            "scratch_dir": "scratch_dir",
+            "manifest_dir": "manifest_dir"
+        }
+
+        result = LambdaBuildContainer._make_request("protocol",
+                                                    "language",
+                                                    "dependency",
+                                                    "application",
+                                                    container_dirs,
+                                                    "manifest_file_name",
+                                                    "runtime",
+                                                    "optimizations",
+                                                    "options")
+
+        self.maxDiff = None  # Print whole json diff
+        self.assertEqual(json.loads(result), {
+            "jsonschema": "2.0",
+            "id": 1,
+            "method": "LambdaBuilder.build",
+            "params": {
+                "__protocol_version": "protocol",
+                "capability": {
+                    "language": "language",
+                    "dependency_manager": "dependency",
+                    "application_framework": "application"
+                },
+                "source_dir": "source_dir",
+                "artifacts_dir": "artifacts_dir",
+                "scratch_dir": "scratch_dir",
+                "manifest_path": "manifest_dir/manifest_file_name",
+                "runtime": "runtime",
+                "optimizations": "optimizations",
+                "options": "options"
+            }
+        })
+
+
+class TestLambdaBuildContainer_get_container_dirs(TestCase):
+
+    def test_must_return_dirs(self):
+        source_dir = "source"
+        manifest_dir = "manifest"
+
+        result = LambdaBuildContainer._get_container_dirs(source_dir, manifest_dir)
+
+        self.assertEquals(result, {
+            "source_dir": "/tmp/samcli/source",
+            "manifest_dir": "/tmp/samcli/manifest",
+            "artifacts_dir": "/tmp/samcli/artifacts",
+            "scratch_dir": "/tmp/samcli/scratch",
+        })
+
+    def test_must_override_manifest_if_equal_to_source(self):
+        source_dir = "/home/source"
+        manifest_dir = "/home/source"
+
+        result = LambdaBuildContainer._get_container_dirs(source_dir, manifest_dir)
+
+        self.assertEquals(result, {
+
+            # When source & manifest directories are the same, manifest_dir must be equal to source
+            "source_dir": "/tmp/samcli/source",
+            "manifest_dir": "/tmp/samcli/source",
+
+            "artifacts_dir": "/tmp/samcli/artifacts",
+            "scratch_dir": "/tmp/samcli/scratch",
+        })
+
+
+class TestLambdaBuildContainer_get_image(TestCase):
+
+    def test_must_get_image_name(self):
+        self.assertEquals("lambci/lambda:build-myruntime", LambdaBuildContainer._get_image("myruntime"))
+
+
+class TestLambdaBuildContainer_get_entrypoint(TestCase):
+
+    def test_must_get_entrypoint(self):
+        self.assertEquals(["lambda-builders", "requestjson"],
+                          LambdaBuildContainer._get_entrypoint("requestjson"))
