diff --git a/releasenotes/notes/tripleo-deploy-working-dir-e0cdf80a82ac256d.yaml b/releasenotes/notes/tripleo-deploy-working-dir-e0cdf80a82ac256d.yaml
new file mode 100644
index 000000000..2f78fa068
--- /dev/null
+++ b/releasenotes/notes/tripleo-deploy-working-dir-e0cdf80a82ac256d.yaml
@@ -0,0 +1,8 @@
+---
+features:
+  - The "openstack tripleo deploy" and "openstack undercloud install" commands
+    now save their generated artifacts from the deployment under a single
+    consistent directory, which by default is located at
+    ~/tripleo-deploy/<stack>. For the undercloud, this location is
+    ~/tripleo-deploy/undercloud. The directory can be overridden with the
+    --output-dir option.
diff --git a/tripleoclient/config/base.py b/tripleoclient/config/base.py
index 389ce5e80..443359a32 100644
--- a/tripleoclient/config/base.py
+++ b/tripleoclient/config/base.py
@@ -14,7 +14,6 @@
 #
 
 from oslo_config import cfg
-from tripleoclient import constants
 
 
 class BaseConfig(object):
@@ -33,11 +32,10 @@ class BaseConfig(object):
         _opts = [
             # TODO(aschultz): rename undercloud_output_dir
             cfg.StrOpt('output_dir',
-                       default=constants.UNDERCLOUD_OUTPUT_DIR,
                        help=(
                            'Directory to output state, processed heat '
-                           'templates, ansible deployment files.'),
-                       ),
+                           'templates, ansible deployment files.'
+                           'Defaults to ~/tripleo-deploy/<stack>')),
             cfg.BoolOpt('cleanup',
                         default=True,
                         help=('Cleanup temporary files. Setting this to '
diff --git a/tripleoclient/config/minion.py b/tripleoclient/config/minion.py
index 03c9993d2..8783faa5c 100644
--- a/tripleoclient/config/minion.py
+++ b/tripleoclient/config/minion.py
@@ -98,9 +98,8 @@ class MinionConfig(StandaloneConfig):
                        help=_(
                            'The name of the file to look for the passwords '
                            'used to connect to the Undercloud. We assume '
-                           'this file is in the folder where the command '
-                           'is executed if a fully qualified path is not '
-                           'provided.')
+                           'this file is in output_dir if a fully qualified '
+                           'path is not provided.')
                        ),
             cfg.StrOpt('minion_undercloud_output_file',
                        default='tripleo-undercloud-outputs.yaml',
diff --git a/tripleoclient/tests/fixture_data/deployment.py b/tripleoclient/tests/fixture_data/deployment.py
index 6a0917888..ce11168f6 100644
--- a/tripleoclient/tests/fixture_data/deployment.py
+++ b/tripleoclient/tests/fixture_data/deployment.py
@@ -54,6 +54,11 @@ class UtilsOvercloudFixture(fixtures.Fixture):
             fixtures.MockPatch(
                 'tripleoclient.utils.update_deployment_status')
         ).mock
+        self.mock_get_default_working_dir = self.useFixture(fixtures.MockPatch(
+            'tripleoclient.utils.get_default_working_dir')
+        ).mock
+        self.mock_get_default_working_dir.return_value = \
+            self.useFixture(fixtures.TempDir()).path
 
 
 class UtilsFixture(fixtures.Fixture):
diff --git a/tripleoclient/utils.py b/tripleoclient/utils.py
index 484968235..0b7f949e3 100644
--- a/tripleoclient/utils.py
+++ b/tripleoclient/utils.py
@@ -1663,14 +1663,10 @@ def build_stack_data(clients, stack_name, template,
     return stack_data
 
 
-def archive_deploy_artifacts(log, stack_name, working_dir,
-                             ansible_dir=None, output_dir=None):
+def archive_deploy_artifacts(log, stack_name, working_dir, ansible_dir=None):
     """Create a tarball of the temporary folders used"""
     log.debug(_("Preserving deployment artifacts"))
 
-    if not output_dir:
-        output_dir = working_dir
-
     def get_tar_filename():
         return os.path.join(
             working_dir, '%s-install-%s.tar.bzip2' %
@@ -1681,7 +1677,7 @@ def archive_deploy_artifacts(log, stack_name, working_dir,
         """Tar filter to remove output dir from path"""
         if info.name.endswith('.bzip2'):
             return None
-        leading_path = output_dir[1:] + '/'
+        leading_path = working_dir[1:] + '/'
         info.name = info.name.replace(leading_path, '')
         return info
 
diff --git a/tripleoclient/v1/minion_config.py b/tripleoclient/v1/minion_config.py
index 2d45949d4..3d02fbafe 100644
--- a/tripleoclient/v1/minion_config.py
+++ b/tripleoclient/v1/minion_config.py
@@ -146,7 +146,13 @@ def prepare_minion_deploy(upgrade=False, no_validations=False,
     # picked up later.
     # NOTE(aschultz): We copy this into the tht root that we save because
     # we move any user provided environment files into this root later.
-    tempdir = os.path.join(os.path.abspath(CONF['output_dir']),
+    if not CONF.get('output_dir'):
+        output_dir = os.path.join(constants.MINION_OUTPUT_DIR,
+                                  'tripleo-deploy',
+                                  'minion')
+    else:
+        output_dir = CONF['output_dir']
+    tempdir = os.path.join(os.path.abspath(output_dir),
                            'tripleo-config-generated-env-files')
     utils.makedirs(tempdir)
 
@@ -219,8 +225,10 @@ def prepare_minion_deploy(upgrade=False, no_validations=False,
     # copy undercloud password file (the configuration is minion_password_file
     # to the place that triple deploy looks for it
     # tripleo-<stack name>-passwords.yaml)
-    _process_undercloud_passwords(CONF['minion_password_file'],
-                                  'tripleo-minion-passwords.yaml')
+    _process_undercloud_passwords(
+        CONF['minion_password_file'],
+        os.path.join(output_dir, 'tripleo-minion-passwords.yaml'))
+
     if upgrade:
         # TODO(aschultz): validate minion upgrade, should be the same as the
         # undercloud one.
@@ -274,8 +282,8 @@ def prepare_minion_deploy(upgrade=False, no_validations=False,
     # TODO(cjeanner) drop that once using oslo.privsep
     deploy_args += ['--deployment-user', u]
 
-    deploy_args += ['--output-dir=%s' % CONF['output_dir']]
-    utils.makedirs(CONF['output_dir'])
+    deploy_args += ['--output-dir=%s' % output_dir]
+    utils.makedirs(output_dir)
 
     # TODO(aschultz): move this to a central class
     if CONF.get('net_config_override', None):
diff --git a/tripleoclient/v1/tripleo_deploy.py b/tripleoclient/v1/tripleo_deploy.py
index e8b4a3799..9e2644d7b 100644
--- a/tripleoclient/v1/tripleo_deploy.py
+++ b/tripleoclient/v1/tripleo_deploy.py
@@ -915,8 +915,8 @@ class Deploy(command.Command):
         parser.add_argument('--output-dir',
                             dest='output_dir',
                             help=_("Directory to output state, processed heat "
-                                   "templates, ansible deployment files."),
-                            default=constants.UNDERCLOUD_OUTPUT_DIR)
+                                   "templates, ansible deployment files.\n"
+                                   "Defaults to ~/tripleo-deploy/<stack>"))
         parser.add_argument('--output-only',
                             dest='output_only',
                             action='store_true',
@@ -1219,7 +1219,13 @@ class Deploy(command.Command):
         self._run_preflight_checks(parsed_args)
 
         # prepare working spaces
-        self.output_dir = os.path.abspath(parsed_args.output_dir)
+        if not parsed_args.output_dir:
+            output_dir = os.path.join(constants.UNDERCLOUD_OUTPUT_DIR,
+                                      'tripleo-deploy',
+                                      parsed_args.stack)
+        else:
+            output_dir = parsed_args.output_dir
+        self.output_dir = os.path.abspath(output_dir)
         self._create_working_dirs(parsed_args.stack.lower())
         # The state that needs to be persisted between serial deployments
         # and cannot be contained in ephemeral heat stacks or working dirs
@@ -1342,8 +1348,6 @@ class Deploy(command.Command):
                 utils.archive_deploy_artifacts(
                     self.log,
                     parsed_args.stack.lower(),
-                    self.tht_render,
-                    self.tmp_ansible_dir,
                     self.output_dir)
 
             if self.ansible_dir:
diff --git a/tripleoclient/v1/undercloud_config.py b/tripleoclient/v1/undercloud_config.py
index 7f340e91d..3740c83fe 100644
--- a/tripleoclient/v1/undercloud_config.py
+++ b/tripleoclient/v1/undercloud_config.py
@@ -453,11 +453,17 @@ def prepare_undercloud_deploy(upgrade=False, no_validations=True,
     utils.configure_logging(LOG, verbose_level, CONF['undercloud_log_file'])
     _load_subnets_config_groups()
 
+    if not CONF.get('output_dir'):
+        output_dir = os.path.join(constants.UNDERCLOUD_OUTPUT_DIR,
+                                  'tripleo-deploy',
+                                  'undercloud')
+    else:
+        output_dir = CONF['output_dir']
     # NOTE(bogdando): the generated env files are stored another path then
     # picked up later.
     # NOTE(aschultz): We copy this into the tht root that we save because
     # we move any user provided environment files into this root later.
-    tempdir = os.path.join(os.path.abspath(CONF['output_dir']),
+    tempdir = os.path.join(os.path.abspath(output_dir),
                            'tripleo-config-generated-env-files')
     utils.makedirs(tempdir)
 
@@ -762,8 +768,8 @@ def prepare_undercloud_deploy(upgrade=False, no_validations=True,
     # TODO(cjeanner) drop that once using oslo.privsep
     deploy_args += ['--deployment-user', u]
 
-    deploy_args += ['--output-dir=%s' % CONF['output_dir']]
-    utils.makedirs(CONF['output_dir'])
+    deploy_args += ['--output-dir=%s' % output_dir]
+    utils.makedirs(output_dir)
 
     if CONF.get('cleanup'):
         deploy_args.append('--cleanup')