diff --git a/conf/taskotron.yaml.example b/conf/taskotron.yaml.example
--- a/conf/taskotron.yaml.example
+++ b/conf/taskotron.yaml.example
@@ -19,6 +19,29 @@
 ## [choices: production, development; default: development]
 #profile: production
 
+## ==== RUNTASK MODE section ====
+## Taskotron supports local task execution as well as execution in remote
+## (virtual) machine.
+## /Local/ runtask mode denotes local execution
+## /Libvirt/ runtask mode denotes that task execution is done in virtual
+## machine, that is created just for purpose of running specified task and
+## upon finishing it is disposed.
+## *The default mode for development profile is /local/ and /libvirt/ for
+## production profile*.
+## [choices: local, libvirt; default: local for development, libvirt for production]
+#runtask_mode: libvirt
+
+## ==== SSH CONNECTION section ====
+## Taskotron connects to remote machine using ssh connection.
+## Connection information can be specified with command line argument
+## --ssh [user@machine[:port]] or here in format /user@machine/ or /user@machine:port/.
+## If -ssh is passed to taskotron without any additional argument, the values
+## are loaded from config. *The default setting is /root@localhost:22/*.
+## You can also specify path to private key, *the default path for
+## private key is conf/id_taskotron * or you can use command line argument
+## --ssh-privkey in the same way as --ssh.
+#ssh: root@localhost:22
+#privkey: conf/id_taskotron
 
 ## ==== REPORTING section ====
 ## This section controls which result reports you want to send after the test
@@ -113,7 +136,7 @@
 ## Configuration of logging level. Here can be configured which messages
 ## will be logged. You can specify different level for logging to standard
 ## output (option log_level_stream) and logging to file (log_level_file).
-## Possible values can be found here: 
+## Possible values can be found here:
 ## https://docs.python.org/2.7/library/logging.html#logging-levels
 #log_level_stream: INFO
 #log_level_file: DEBUG
@@ -131,4 +154,3 @@
 ## These credentials are used when reporting results into Bodhi.
 #fas_username: taskotron
 #fas_password: ''
-
diff --git a/libtaskotron/config.py b/libtaskotron/config.py
--- a/libtaskotron/config.py
+++ b/libtaskotron/config.py
@@ -14,7 +14,7 @@
 from libtaskotron import exceptions as exc
 from libtaskotron.logger import log
 from libtaskotron.config_defaults import (Config, ProductionConfig,
-                                          TestingConfig, ProfileName)
+                                          TestingConfig, ProfileName, RuntaskModeName)
 from libtaskotron import file_utils
 
 CONF_DIRS = [ # local checkout dir first, then system wide dir
@@ -101,6 +101,16 @@
     # merge the changes from the config file
     _merge_config(config, file_config)
 
+    # check if runtaskmode is set properly
+    if config.runtask_mode not in [RuntaskModeName.LOCAL, RuntaskModeName.LIBVIRT]:
+        if config.profile == ProfileName.PRODUCTION:
+            runtask_mode = RuntaskModeName.LIBVIRT
+        else:
+            runtask_mode = RuntaskModeName.LOCAL
+        log.warn('Unknown runtask_mode "%s", selecting "%s"', config.runtask_mode,
+                 runtask_mode)
+        config.runtask_mode = runtask_mode
+
     # set config filename used, this is set after merging
     # so it doesn't get overridden
     config.config_filename = filename
diff --git a/libtaskotron/config_defaults.py b/libtaskotron/config_defaults.py
--- a/libtaskotron/config_defaults.py
+++ b/libtaskotron/config_defaults.py
@@ -20,6 +20,14 @@
     TESTING = 'testing'          #:
 
 
+class RuntaskModeName(object):
+    '''Enum of available runtask mode names. These can be specified in
+    the config file'''
+
+    LIBVIRT = 'libvirt'  #:
+    LOCAL = 'local'      #:
+
+
 class Config(object):
     '''Global configuration for Taskotron (development profile).
 
@@ -46,6 +54,11 @@
 
     profile = ProfileName.DEVELOPMENT                                       #:
 
+    ssh = 'root@localhost:22'                                               #:
+    privkey = 'conf/id_taskotron'                                           #:
+
+    runtask_mode = 'local'                                                  #:
+
     reporting_enabled = False                                               #:
     report_to_bodhi = True                                                  #:
     report_to_resultsdb = True                                              #:
@@ -90,6 +103,8 @@
 
     profile = ProfileName.PRODUCTION                                        #:
 
+    runtask_mode = 'libvirt'                                                #:
+
     reporting_enabled = True                                                #:
 
     download_cache_enabled = False                                          #:
diff --git a/libtaskotron/rpm_utils.py b/libtaskotron/rpm_utils.py
--- a/libtaskotron/rpm_utils.py
+++ b/libtaskotron/rpm_utils.py
@@ -134,6 +134,9 @@
     :return: True if all specified packages are installed, False otherwise
     :rtype: bool
     '''
+    if not pkgs:
+        return True
+
     cmd = ['dnf', '--assumeno', '--disableplugin=noroot', 'install']
     cmd.extend(pkgs)
 
diff --git a/libtaskotron/runner.py b/libtaskotron/runner.py
--- a/libtaskotron/runner.py
+++ b/libtaskotron/runner.py
@@ -49,18 +49,23 @@
         self.uuid_dir = uuid_dir
 
         # decide whether to run on disposable client
-        # TODO: remove this section once T408 is implemented
-        try:
-            self.run_remotely = self.task_data['environment']['machine'] is not None
-        except KeyError:
+        # TODO: at the moment, runtask_mode='libvirt' actually means execution over ssh, this
+        #       will change when T408 is done
+        runtask_mode = config.get_config().runtask_mode
+        if runtask_mode == config.RuntaskModeName.LIBVIRT:
+            self.run_remotely = True
+        elif runtask_mode == config.RuntaskModeName.LOCAL:
             self.run_remotely = False
+        else:
+            assert False, 'This should never occur'
 
         # if --local was passed, override local/disposable decision
         if self.arg_data['local']:
             self.run_remotely = False
             log.debug("Overriding local/disposable decision, running locally (flag --local)")
 
-        if self.arg_data['ssh']:
+        # if --ssh was passed, override local/disposable decision
+        elif self.arg_data['ssh']:
             self.run_remotely = True
 
             self.user, machine = self.arg_data['ssh'].split('@')
@@ -71,19 +76,9 @@
                 self.machine = machine
                 self.port = 22
 
-            if 'ssh_privkey' in self.arg_data:
-                self.privkey = self.arg_data['ssh_privkey']
-            else:
-                # TODO: load key location from config
-                self.privkey = None
+            self.privkey = self.arg_data['ssh_privkey']
 
             log.debug("Overriding local/disposable decision, running over ssh (arg --ssh)")
-        else:
-            # TODO: move this to config default
-            self.user = 'root'
-            self.machine = '192.168.122.99'
-            self.port = 22
-            self.privkey = 'conf/id_taskotron'
 
     def run(self):
         '''Run the apropriate code based on previous local/remote decision.'''
@@ -265,7 +260,7 @@
 
         profile = config.get_config().profile
 
-        if profile == config.ProfileName.PRODUCTION:  # TODO: or local/bare metal
+        if profile == config.ProfileName.PRODUCTION or self.envdata['local']:
             rpm_utils.install(rpms)
         else:
             if not rpm_utils.is_installed(rpms):
@@ -370,12 +365,14 @@
                              "This also approves any required system-wide changes to be performed "
                              "(automatic installation of package dependencies, destructive tasks "
                              "allowed, etc).")
-    parser.add_argument("--ssh",
+    parser.add_argument("--ssh", nargs='?',
                         metavar='user@machine[:port]',
+                        default=None, const=config.get_config().ssh,
                         help="make the task run over ssh on remote machine, regardless of "
                              "the default execution mode defined in the configuration file.")
-    parser.add_argument("--ssh-privkey",
+    parser.add_argument("--ssh-privkey", nargs='?',
                         metavar="<path to private key>",
+                        default=None, const=config.get_config().privkey,
                         help="provide path to private key for ssh connection to remote machine.")
 
     return parser
diff --git a/testing/test_runner.py b/testing/test_runner.py
--- a/testing/test_runner.py
+++ b/testing/test_runner.py
@@ -473,7 +473,7 @@
         monkeypatch.setattr(rpm_utils, 'is_installed', self.is_installed_stub)
         monkeypatch.setattr(config, 'get_config', self.stub_get_config)
 
-        self.test_runner = runner.LocalRunner({}, {})
+        self.test_runner = runner.LocalRunner({}, {'local': False, 'ssh': None})
 
     def test_devel_profile_installed(self, monkeypatch):
         self.is_installed_stub.return_value = True
@@ -512,49 +512,66 @@
         monkeypatch.setattr(runner.Runner, '_run_locally', self.stub_local)
         monkeypatch.setattr(runner.Runner, '_run_remotely', self.stub_remote)
 
+        self.stub_get_config = Dingus()
+        self.stub_config_local = Dingus()
+        self.stub_config_local.runtask_mode = config.RuntaskModeName.LOCAL
+        self.stub_config_remote = Dingus()
+        self.stub_config_remote.runtask_mode = config.RuntaskModeName.LIBVIRT
+
     def test_bare_no_local(self, monkeypatch):
-        ref_inputdata = {'local': False, 'ssh': None}
-        ref_taskdata = {
-            'environment': {'rpm': ['foo', 'bar']}
-        }
+        ref_inputdata = {'local': False, 'ssh': None, 'ssh_privkey': None}
+        ref_taskdata = {'environment': {'rpm': ['foo', 'bar']}}
+        self.stub_get_config.return_value = self.stub_config_local
+        monkeypatch.setattr(config, 'get_config', self.stub_get_config)
 
         test_runner = runner.Runner(ref_taskdata, ref_inputdata, self.uuid_dir)
 
         test_runner.run()
 
         assert len(self.stub_local.calls()) == 1 and len(self.stub_remote.calls()) == 0
 
     def test_remote_no_local(self, monkeypatch):
-        ref_inputdata = {'local': False, 'ssh': None}
-        ref_taskdata = {
-            'environment': {'machine': 'HAL9000', 'rpm': ['foo', 'bar']}
-        }
+        ref_inputdata = {'local': False, 'ssh': None, 'ssh_privkey': None}
+        ref_taskdata = {'environment': {'rpm': ['foo', 'bar']}}
+        self.stub_get_config.return_value = self.stub_config_remote
+        monkeypatch.setattr(config, 'get_config', self.stub_get_config)
 
         test_runner = runner.Runner(ref_taskdata, ref_inputdata, self.uuid_dir)
 
         test_runner.run()
 
         assert len(self.stub_local.calls()) == 0 and len(self.stub_remote.calls()) == 1
 
     def test_remote_override_local(self, monkeypatch):
-        ref_inputdata = {'local': True, 'ssh': None}
-        ref_taskdata = {
-            'environment': {'machine': 'HAL9000', 'rpm': ['foo', 'bar']}
-        }
+        ref_inputdata = {'local': True, 'ssh': None, 'ssh_privkey': None}
+        ref_taskdata = {'environment': {'rpm': ['foo', 'bar']}}
+        self.stub_get_config.return_value = self.stub_config_remote
+        monkeypatch.setattr(config, 'get_config', self.stub_get_config)
 
         test_runner = runner.Runner(ref_taskdata, ref_inputdata, self.uuid_dir)
 
         test_runner.run()
 
         assert len(self.stub_local.calls()) == 1 and len(self.stub_remote.calls()) == 0
 
+    def test_bare_override_ssh(self, monkeypatch):
+        ref_inputdata = {'local': False, 'ssh': 'david@HAL9000', 'ssh_privkey': None}
+        ref_taskdata = {'environment': {'rpm': ['foo', 'bar']}}
+        self.stub_get_config.return_value = self.stub_config_local
+        monkeypatch.setattr(config, 'get_config', self.stub_get_config)
+
+        test_runner = runner.Runner(ref_taskdata, ref_inputdata, self.uuid_dir)
+
+        test_runner.run()
+
+        assert len(self.stub_local.calls()) == 0 and len(self.stub_remote.calls()) == 1
+
 
 class TestRunnerLocalRunner():
     def test_local_runner_used(self, monkeypatch):
-        ref_inputdata = {'local': True, 'ssh': None}
-        ref_taskdata = {
-            'environment': {'machine': 'HAL9000', 'rpm': ['foo', 'bar']}
-        }
+        ref_inputdata = {'local': True, 'ssh': None, 'ssh_privkey': None}
+        ref_taskdata = {'environment': {'rpm': ['foo', 'bar']}}
+
         uuid_dir = '/something/totally/random'
         stub_local_runner = Dingus()
         monkeypatch.setattr(runner, 'LocalRunner', stub_local_runner)
@@ -567,10 +584,9 @@
         assert len(stub_local_runner.calls()) == 1
 
     def test_local_runner_properly_used(self, monkeypatch):
-        ref_inputdata = {'local': True, 'ssh': None}
-        ref_taskdata = {
-            'environment': {'machine': 'HAL9000', 'rpm': ['foo', 'bar']}
-        }
+        ref_inputdata = {'local': True, 'ssh': None, 'ssh_privkey': None}
+        ref_taskdata = {'environment': {'rpm': ['foo', 'bar']}}
+
         uuid_dir = '/something/totally/random'
         stub_local_runner_object = Dingus()
         stub_local_runner = Dingus()
@@ -587,7 +603,7 @@
         assert 'prepare_task' in methods_called and 'run' in methods_called
 
     def test_ssh_bad_usermachine(self, monkeypatch):
-        ref_inputdata = {'local': False, 'ssh': 'user#machine'}
+        ref_inputdata = {'local': False, 'ssh': 'user#machine', 'ssh_privkey': None}
 
         stub_error = Dingus()
         stub_parser = Dingus()
@@ -597,7 +613,7 @@
         assert len(stub_error.calls()) == 1
 
     def test_local_ssh_together(self, monkeypatch):
-        ref_inputdata = {'local': True, 'ssh': 'user@machine'}
+        ref_inputdata = {'local': True, 'ssh': 'user@machine', 'ssh_privkey': None}
 
         stub_error = Dingus()
         stub_parser = Dingus()

