diff --git a/libtaskotron/taskformula.py b/libtaskotron/taskformula.py
--- a/libtaskotron/taskformula.py
+++ b/libtaskotron/taskformula.py
@@ -109,20 +109,18 @@
 
 
 def replace_vars_in_action(action, variables):
-    '''Find all variables that are leaves (in a tree sense) in an action. Leaves
-    are variables which can no longer be traversed, i.e. "primitive" types like
-    ``str`` or ``int``. Non-leaves are containers like ``dict`` or ``list``.
+    '''Replace variables (${var} or ${var.key}) with their values in an action (YAML dictionary).
 
-    For all leaves, call :func:`_replace_vars` and update their value with
-    the function's output.
+    Only basestring values are searched for variables syntax, which means variables can either be
+    inside leaves (in a tree sense, meaning they can't be traversed further and are of a primitive
+    type), or inside key names in a dictionary.
 
-    :param dict action: An action specification parsed from the task formula.
-                        See :meth:`.Runner.do_actions` to see what an action
-                        looks like.
+    :param dict action: An action specification parsed from the task formula. See
+                        :meth:`.Runner.do_actions` to see what an action looks like.
     :param dict variables: names (keys) and values of variables to replace
-    :raise TaskotronYamlError: if ``text`` contains a variable that is not
-                               present in ``variables``, or if the variable
-                               syntax is incorrect
+    :raise TaskotronYamlError: if ``text`` contains a variable that is not present in
+                               ``variables``; or if the variable syntax is incorrect; of if you try
+                               to replace a dictionary key with something else than a string
     '''
 
     visited = []  # all visited nodes in a tree
@@ -135,15 +133,15 @@
             continue
 
         visited.append(vertex)
-        children = [] # list of tuples (index/key, child_value)
+        children = []  # list of tuples (index/key, child_value)
 
         if isinstance(vertex, collections.MutableMapping):
             children = vertex.items()  # list of (key, value)
         elif isinstance(vertex, collections.MutableSequence):
             children = list(enumerate(vertex))  # list of (index, value)
         else:
-            log.warn("Unknown structure '%s' in YAML file, this shouldn't "
-                     "happen: %s", type(vertex), vertex)
+            log.warn("Unknown structure '%s' in YAML file, this shouldn't happen: %s",
+                     type(vertex), vertex)
 
         for index, child_val in children:
             if isinstance(child_val, basestring):
@@ -153,6 +151,18 @@
                 # traversable further down, mark for visit
                 stack.append(child_val)
 
+            # for dicts, also replace vars in key names
+            if isinstance(index, basestring):  # meaning it's a dictionary
+                new_key = _replace_vars(index, variables)
+                if not isinstance(new_key, basestring):
+                    raise exc.TaskotronYamlError(
+                        "Dictionary key '%s' can't be replaced with value %r, because it's not a "
+                        "string, but %s" % (index, new_key, type(new_key)))
+                # only swap keys if the new key is actually different from the old one
+                if new_key != index:
+                    vertex[new_key] = vertex[index]
+                    del vertex[index]
+
 
 def devise_environment(formula, arg_data):
     '''Takes a parsed formula, and returns a required run-environment (i.e.
diff --git a/testing/test_taskformula.py b/testing/test_taskformula.py
--- a/testing/test_taskformula.py
+++ b/testing/test_taskformula.py
@@ -48,6 +48,7 @@
         with pytest.raises(AttributeError):
             taskformula._get_key_or_attribute({"foo": None}, "foo.bar")
 
+
 class TestReplaceVarsInAction(object):
 
     def setup_method(self, method):
@@ -65,6 +66,7 @@
                 'from_dict': '${dict.pi}',
                 'from_object': '${number.imag}',
                 'from_mixed': '${dict.pi.imag}',
+                '${dictkey}': '$foo',
             }
         }
         self.vars = {
@@ -75,33 +77,51 @@
             'dict': {'key': 'val', 'pi': 3.14},
             'null': None,
             'extra': 'juicy',
+            'dictkey': 'a brand new key name',
         }
 
-    def test_complex(self):
+    def test_full_replace(self):
         rend_action = copy.deepcopy(self.action)
         rend_action['name'] = 'run foo%s' % self.vars['foo']
         rend_action['export'] = self.vars['export']
         rend_action['python']['args'] = ['arg1', self.vars['number'], 'arg2',
                                          self.vars['list'], self.vars['dict']]
-        rend_action['python']['cmdline'] = '-s %s -i %s' % (self.vars['foo'],
-                                                            self.vars['number'])
+        rend_action['python']['cmdline'] = '-s %s -i %s' % (self.vars['foo'], self.vars['number'])
         rend_action['python']['nest']['deepnest'] = self.vars['dict']
         rend_action['python']['nest']['null'] = self.vars['null']
         rend_action['python']['from_dict'] = self.vars['dict']['pi']
         rend_action['python']['from_object'] = self.vars['number'].imag
         rend_action['python']['from_mixed'] = self.vars['dict']['pi'].imag
+        del rend_action['python']['${dictkey}']
+        rend_action['python'][self.vars['dictkey']] = self.vars['foo']
 
         taskformula.replace_vars_in_action(self.action, self.vars)
         assert self.action == rend_action
 
-    def test_raise(self):
+    def test_raise_missing_all_vars(self):
         with pytest.raises(exc.TaskotronYamlError):
             taskformula.replace_vars_in_action(self.action, {})
 
+    def test_raise_missing_one_var(self):
         del self.vars['number']
         with pytest.raises(exc.TaskotronYamlError):
             taskformula.replace_vars_in_action(self.action, self.vars)
 
+    def test_raise_dictkey_not_string(self):
+        '''It's not possible to replace dict keys with anything but a string'''
+        self.vars['dictkey'] = {'non-string': 'value'}
+        with pytest.raises(exc.TaskotronYamlError):
+            taskformula.replace_vars_in_action(self.action, self.vars)
+
+    def test_replace_dictkey_with_itself(self):
+        '''After replacing dict key with exactly the same string, the key must still be present.
+        An obvious bug would be to have that key removed (because the implementation would try
+        to remove the "old" key)'''
+        self.vars['dictkey'] = '${dictkey}'  # I'm so sneaky
+        taskformula.replace_vars_in_action(self.action, self.vars)
+        assert '${dictkey}' in self.action['python']
+        assert self.action['python']['${dictkey}'] == self.vars['foo']
+
 
 class TestReplaceVars(object):
 

