diff --git a/rootfs/api/migrations/0024_config_lifecycle_hooks.py b/rootfs/api/migrations/0024_config_lifecycle_hooks.py
new file mode 100644
index 000000000..c23d80f9c
--- /dev/null
+++ b/rootfs/api/migrations/0024_config_lifecycle_hooks.py
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.2 on 2017-06-22 18:42
+from __future__ import unicode_literals
+
+from django.db import migrations
+import jsonfield.fields
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('api', '0023_app_k8s_name_length'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='config',
+            name='lifecycle_post_start',
+            field=jsonfield.fields.JSONField(blank=True, default={}),
+        ),
+        migrations.AddField(
+            model_name='config',
+            name='lifecycle_pre_stop',
+            field=jsonfield.fields.JSONField(blank=True, default={}),
+        ),
+    ]
diff --git a/rootfs/api/migrations/0025_app_procfile_structure.py b/rootfs/api/migrations/0025_app_procfile_structure.py
new file mode 100644
index 000000000..efee506e8
--- /dev/null
+++ b/rootfs/api/migrations/0025_app_procfile_structure.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.2 on 2017-08-14 20:45
+from __future__ import unicode_literals
+
+import api.models.app
+from django.db import migrations
+import jsonfield.fields
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('api', '0024_config_lifecycle_hooks'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='app',
+            name='procfile_structure',
+            field=jsonfield.fields.JSONField(blank=True, default={}, validators=[api.models.app.validate_app_structure]),
+        ),
+    ]
diff --git a/rootfs/api/models/app.py b/rootfs/api/models/app.py
index 219bb6c2c..cec6d9a3b 100644
--- a/rootfs/api/models/app.py
+++ b/rootfs/api/models/app.py
@@ -71,6 +71,7 @@ class App(UuidAuditedModel):
                           validators=[validate_app_id,
                                       validate_reserved_names])
     structure = JSONField(default={}, blank=True, validators=[validate_app_structure])
+    procfile_structure = JSONField(default={}, blank=True, validators=[validate_app_structure])
 
     class Meta:
         verbose_name = 'Application'
@@ -408,6 +409,7 @@ def scale(self, user, structure):  # noqa
         if new_structure != self.structure:
             # save new structure to the database
             self.structure = new_structure
+            self.procfile_structure = release.build.procfile
             self.save()
 
             try:
@@ -474,6 +476,7 @@ def deploy(self, release, force_deploy=False, rollback_on_failure=True):  # noqa
         # set processes structure to default if app is new.
         if self.structure == {}:
             self.structure = self._default_structure(release)
+            self.procfile_structure = self._default_structure(release)
             self.save()
         # reset canonical process types if build type has changed.
         else:
@@ -489,8 +492,18 @@ def deploy(self, release, force_deploy=False, rollback_on_failure=True):  # noqa
                     # update with the default process type.
                     structure.update(self._default_structure(release))
                     self.structure = structure
+                    # if procfile structure exists then we use it
+                    if release.build.procfile and \
+                       release.build.sha and not \
+                       release.build.dockerfile:
+                        self.procfile_structure = release.build.procfile
                     self.save()
 
+        # always set the procfile structure for any new release
+        if release.build.procfile:
+            self.procfile_structure = release.build.procfile
+            self.save()
+
         # deploy application to k8s. Also handles initial scaling
         app_settings = self.appsettings_set.latest()
         deploys = {}
@@ -1086,6 +1099,8 @@ def _gather_app_settings(self, release, app_settings, process_type, replicas):
             'app_type': process_type,
             'build_type': release.build.type,
             'healthcheck': healthcheck,
+            'lifecycle_post_start': config.lifecycle_post_start,
+            'lifecycle_pre_stop': config.lifecycle_pre_stop,
             'routable': routable,
             'deploy_batches': batches,
             'deploy_timeout': deploy_timeout,
diff --git a/rootfs/api/models/config.py b/rootfs/api/models/config.py
index 2e69006d5..4ab1145e6 100644
--- a/rootfs/api/models/config.py
+++ b/rootfs/api/models/config.py
@@ -18,6 +18,8 @@ class Config(UuidAuditedModel):
     app = models.ForeignKey('App', on_delete=models.CASCADE)
     values = JSONField(default={}, blank=True)
     memory = JSONField(default={}, blank=True)
+    lifecycle_post_start = JSONField(default={}, blank=True)
+    lifecycle_pre_stop = JSONField(default={}, blank=True)
     cpu = JSONField(default={}, blank=True)
     tags = JSONField(default={}, blank=True)
     registry = JSONField(default={}, blank=True)
@@ -162,7 +164,8 @@ def save(self, **kwargs):
                 # usually means a totally new app
                 previous_config = self.app.config_set.latest()
 
-            for attr in ['cpu', 'memory', 'tags', 'registry', 'values']:
+            for attr in ['cpu', 'memory', 'tags', 'registry', 'values',
+                         'lifecycle_post_start', 'lifecycle_pre_stop']:
                 data = getattr(previous_config, attr, {}).copy()
                 new_data = getattr(self, attr, {}).copy()
 
diff --git a/rootfs/api/models/release.py b/rootfs/api/models/release.py
index 1ccbbd436..330540b1e 100644
--- a/rootfs/api/models/release.py
+++ b/rootfs/api/models/release.py
@@ -424,6 +424,40 @@ def save(self, *args, **kwargs):  # noqa
                     changes = 'changed limits for '+', '.join(changes)
                     self.summary += "{} {}".format(self.config.owner, changes)
 
+                # if the lifecycle_post_start hooks changed, log the dict diff
+                changes = []
+                old_lifecycle_post_start = old_config.lifecycle_post_start if old_config else {}
+                diff = dict_diff(self.config.lifecycle_post_start, old_lifecycle_post_start)
+                # try to be as succinct as possible
+                added = ', '.join(k for k in diff.get('added', {}))
+                added = 'added lifecycle_post_start  ' + added if added else ''
+                changed = ', '.join(k for k in diff.get('changed', {}))
+                changed = 'changed lifecycle_post_start ' + changed if changed else ''
+                deleted = ', '.join(k for k in diff.get('deleted', {}))
+                deleted = 'deleted lifecycle_post_start ' + deleted if deleted else ''
+                changes = ', '.join(i for i in (added, changed, deleted) if i)
+                if changes:
+                    if self.summary:
+                        self.summary += ' and '
+                    self.summary += "{} {}".format(self.config.owner, changes)
+
+                # if the lifecycle_pre_stop hooks changed, log the dict diff
+                changes = []
+                old_lifecycle_pre_stop = old_config.lifecycle_pre_stop if old_config else {}
+                diff = dict_diff(self.config.lifecycle_pre_stop, old_lifecycle_pre_stop)
+                # try to be as succinct as possible
+                added = ', '.join(k for k in diff.get('added', {}))
+                added = 'added lifecycle_pre_stop  ' + added if added else ''
+                changed = ', '.join(k for k in diff.get('changed', {}))
+                changed = 'changed lifecycle_pre_stop ' + changed if changed else ''
+                deleted = ', '.join(k for k in diff.get('deleted', {}))
+                deleted = 'deleted lifecycle_pre_stop ' + deleted if deleted else ''
+                changes = ', '.join(i for i in (added, changed, deleted) if i)
+                if changes:
+                    if self.summary:
+                        self.summary += ' and '
+                    self.summary += "{} {}".format(self.config.owner, changes)
+
                 # if the tags changed, log the dict diff
                 changes = []
                 old_tags = old_config.tags if old_config else {}
diff --git a/rootfs/api/serializers.py b/rootfs/api/serializers.py
index 46a46fb77..b91cc8681 100644
--- a/rootfs/api/serializers.py
+++ b/rootfs/api/serializers.py
@@ -172,11 +172,12 @@ class AppSerializer(serializers.ModelSerializer):
 
     owner = serializers.ReadOnlyField(source='owner.username')
     structure = serializers.JSONField(required=False)
+    procfile_structure = serializers.JSONField(required=False)
 
     class Meta:
         """Metadata options for a :class:`AppSerializer`."""
         model = models.App
-        fields = ['uuid', 'id', 'owner', 'structure', 'created', 'updated']
+        fields = ['uuid', 'id', 'owner', 'structure', 'procfile_structure', 'created', 'updated']
 
 
 class BuildSerializer(serializers.ModelSerializer):
@@ -210,6 +211,8 @@ class ConfigSerializer(serializers.ModelSerializer):
     owner = serializers.ReadOnlyField(source='owner.username')
     values = JSONFieldSerializer(required=False, binary=True)
     memory = JSONFieldSerializer(required=False, binary=True)
+    lifecycle_post_start = JSONFieldSerializer(required=False, binary=True)
+    lifecycle_pre_stop = JSONFieldSerializer(required=False, binary=True)
     cpu = JSONFieldSerializer(required=False, binary=True)
     tags = JSONFieldSerializer(required=False, binary=True)
     registry = JSONFieldSerializer(required=False, binary=True)
diff --git a/rootfs/api/tests/test_app.py b/rootfs/api/tests/test_app.py
index 40dcb4197..5b4b3bf0f 100644
--- a/rootfs/api/tests/test_app.py
+++ b/rootfs/api/tests/test_app.py
@@ -90,7 +90,8 @@ def test_response_data(self, mock_requests):
         body = {'id': 'app-{}'.format(random.randrange(1000, 10000))}
         response = self.client.post('/v2/apps', body)
         for key in response.data:
-            self.assertIn(key, ['uuid', 'created', 'updated', 'id', 'owner', 'structure'])
+            self.assertIn(key, ['uuid', 'created', 'updated', 'id', 'owner', 'structure',
+                                'procfile_structure'])
         expected = {
             'id': body['id'],
             'owner': self.user.username,
diff --git a/rootfs/api/tests/test_config.py b/rootfs/api/tests/test_config.py
index c65721af3..08f2a0834 100644
--- a/rootfs/api/tests/test_config.py
+++ b/rootfs/api/tests/test_config.py
@@ -165,7 +165,8 @@ def test_response_data(self, mock_requests):
         response = self.client.post(url, body)
         for key in response.data:
             self.assertIn(key, ['uuid', 'owner', 'created', 'updated', 'app', 'values', 'memory',
-                                'cpu', 'tags', 'registry', 'healthcheck'])
+                                'cpu', 'tags', 'registry', 'healthcheck', 'lifecycle_post_start',
+                                'lifecycle_pre_stop'])
         expected = {
             'owner': self.user.username,
             'app': app_id,
@@ -188,7 +189,8 @@ def test_response_data_types_converted(self, mock_requests):
         self.assertEqual(response.status_code, 201, response.data)
         for key in response.data:
             self.assertIn(key, ['uuid', 'owner', 'created', 'updated', 'app', 'values', 'memory',
-                                'cpu', 'tags', 'registry', 'healthcheck'])
+                                'cpu', 'tags', 'registry', 'healthcheck', 'lifecycle_post_start',
+                                'lifecycle_pre_stop'])
         expected = {
             'owner': self.user.username,
             'app': app_id,
diff --git a/rootfs/scheduler/resources/pod.py b/rootfs/scheduler/resources/pod.py
index b8ad48c65..f1c0e62b8 100644
--- a/rootfs/scheduler/resources/pod.py
+++ b/rootfs/scheduler/resources/pod.py
@@ -224,6 +224,8 @@ def _set_container(self, namespace, container_name, data, **kwargs):
 
         self._set_health_checks(data, env, **kwargs)
 
+        self._set_lifecycle_hooks(data, env, **kwargs)
+
     def _set_resources(self, container, kwargs):
         """ Set CPU/memory resource management manifest """
         app_type = kwargs.get("app_type")
@@ -278,6 +280,38 @@ def _set_health_checks(self, container, env, **kwargs):
         elif kwargs.get('routable', False):
             self._default_readiness_probe(container, kwargs.get('build_type'), env.get('PORT', None))  # noqa
 
+    def _set_lifecycle_hooks(self, container, env, **kwargs):
+        app_type = kwargs.get("app_type")
+        lifecycle_post_start = kwargs.get('lifecycle_post_start', {})
+        lifecycle_post_start = lifecycle_post_start.get(app_type)
+        lifecycle_pre_stop = kwargs.get('lifecycle_pre_stop', {})
+        lifecycle_pre_stop = lifecycle_pre_stop.get(app_type)
+        lifecycle = defaultdict(dict)
+        if lifecycle_post_start or lifecycle_pre_stop:
+            lifecycle = defaultdict(dict)
+
+            if lifecycle_post_start:
+                lifecycle["postStart"] = {
+                        'exec': {
+                            "command": [
+                                "/bin/bash",
+                                "-c",
+                                "{0}".format(lifecycle_post_start)
+                            ]
+                        }
+                }
+            if lifecycle_pre_stop:
+                lifecycle["preStop"] = {
+                        'exec': {
+                            "command": [
+                                "/bin/bash",
+                                "-c",
+                                "{0}".format(lifecycle_pre_stop)
+                            ]
+                        }
+                }
+            container["lifecycle"] = dict(lifecycle)
+
     def _default_readiness_probe(self, container, build_type, port=None):
         # Update only the application container with the health check
         if build_type == "buildpack":
@@ -345,6 +379,15 @@ def _default_dockerapp_readiness_probe(self, port, delay=5, timeout=5, period_se
         }
         return readinessprobe
 
+    def _set_custom_termination_period(self, container, period_seconds=900):
+        """
+        Applies a custom terminationGracePeriod only if provided as env variable.
+        """
+        terminationperiod = {
+            'terminationGracePeriodSeconds': int(period_seconds)
+        }
+        container.update(terminationperiod)
+
     def delete(self, namespace, name):
         # get timeout info from pod
         pod = self.pod.get(namespace, name).json()