diff --git a/app/app.go b/app/app.go
new file mode 100644
index 0000000000000000000000000000000000000000..4760b59a01aa77bfc31213ee2419f22039ed755a
--- /dev/null
+++ b/app/app.go
@@ -0,0 +1,102 @@
+package app
+
+import (
+	"os"
+	"path"
+
+	"github.com/sirupsen/logrus"
+	"github.com/urfave/cli"
+
+	"gitlab.com/gitlab-org/gitlab-runner/common"
+	"gitlab.com/gitlab-org/gitlab-runner/log"
+)
+
+var (
+	authors = []cli.Author{
+		{
+			Name:  "GitLab Inc.",
+			Email: "support@gitlab.com",
+		},
+	}
+)
+
+type Handler func(cliCtx *cli.Context) error
+type Handlers []Handler
+
+func (a *Handlers) Handle(cliCtx *cli.Context) error {
+	for _, f := range *a {
+		err := f(cliCtx)
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
+type App struct {
+	app *cli.App
+
+	beforeFunctions Handlers
+	afterFunctions  Handlers
+}
+
+func (a *App) init(usage string) {
+	app := cli.NewApp()
+	a.app = app
+
+	app.Name = path.Base(os.Args[0])
+	app.Usage = usage
+	app.Authors = authors
+	app.Version = common.AppVersion.ShortLine()
+	cli.VersionPrinter = common.AppVersion.Printer
+
+	app.Commands = common.GetCommands()
+	app.CommandNotFound = func(cliCtx *cli.Context, command string) {
+		logrus.Fatalf("Command %s not found", command)
+	}
+
+	a.beforeFunctions = make(Handlers, 0)
+	app.Before = a.beforeFunctions.Handle
+
+	a.afterFunctions = make(Handlers, 0)
+	app.After = a.afterFunctions.Handle
+}
+
+func (a *App) Run() {
+	if err := a.app.Run(os.Args); err != nil {
+		logrus.WithError(err).Fatal("Application execution failed")
+	}
+}
+
+func (a *App) Extend(extension func(*cli.App)) {
+	extension(a.app)
+}
+
+func (a *App) AppendBeforeFunc(f Handler) {
+	a.beforeFunctions = append(a.beforeFunctions, f)
+}
+
+func (a *App) AppendAfterFunc(f Handler) {
+	a.afterFunctions = append(a.afterFunctions, f)
+}
+
+func New(usage string) *App {
+	app := new(App)
+	app.init(usage)
+	app.Extend(log.AddFlags)
+	app.AppendBeforeFunc(log.ConfigureLogging)
+
+	return app
+}
+
+func Recover() {
+	r := recover()
+	if r != nil {
+		// log panics forces exit
+		if _, ok := r.(*logrus.Entry); ok {
+			os.Exit(1)
+		}
+		panic(r)
+	}
+}
diff --git a/app/cpuprofile.go b/app/cpuprofile.go
new file mode 100644
index 0000000000000000000000000000000000000000..94caabb7f643e57657b73ba6b9fb5e0c562e192a
--- /dev/null
+++ b/app/cpuprofile.go
@@ -0,0 +1,33 @@
+package app
+
+import (
+	"os"
+	"runtime/pprof"
+
+	"github.com/urfave/cli"
+)
+
+func CPUProfileFlags(app *cli.App) {
+	app.Flags = append(app.Flags, cli.StringFlag{
+		Name:   "cpuprofile",
+		Usage:  "write cpu profile to file",
+		EnvVar: "CPU_PROFILE",
+	})
+}
+
+func CPUProfileSetup(cliCtx *cli.Context) error {
+	if cpuProfile := cliCtx.String("cpuprofile"); cpuProfile != "" {
+		f, err := os.Create(cpuProfile)
+		if err != nil {
+			return err
+		}
+		pprof.StartCPUProfile(f)
+	}
+
+	return nil
+}
+
+func CPUProfileTeardown(cliCtx *cli.Context) error {
+	pprof.StopCPUProfile()
+	return nil
+}
diff --git a/app/fix_home.go b/app/fix_home.go
new file mode 100644
index 0000000000000000000000000000000000000000..1a468779298b7f02b5aa5e5aaf0f786ca545e60e
--- /dev/null
+++ b/app/fix_home.go
@@ -0,0 +1,22 @@
+package app
+
+import (
+	"fmt"
+	"os"
+
+	"github.com/docker/docker/pkg/homedir"
+	"github.com/urfave/cli"
+)
+
+func FixHOME(cliCtx *cli.Context) error {
+	// Fix home
+	if key := homedir.Key(); os.Getenv(key) == "" {
+		value := homedir.Get()
+		if value == "" {
+			return fmt.Errorf("the %q is not set", key)
+		}
+		os.Setenv(key, value)
+	}
+
+	return nil
+}
diff --git a/app/runtime_platform.go b/app/runtime_platform.go
new file mode 100644
index 0000000000000000000000000000000000000000..849d6f9c9c09b78a0a88637d13ba32f002ab9c49
--- /dev/null
+++ b/app/runtime_platform.go
@@ -0,0 +1,25 @@
+package app
+
+import (
+	"os"
+	"runtime"
+
+	"github.com/sirupsen/logrus"
+	"github.com/urfave/cli"
+
+	"gitlab.com/gitlab-org/gitlab-runner/common"
+)
+
+func LogRuntimePlatform(cliCtx *cli.Context) error {
+	fields := logrus.Fields{
+		"os":       runtime.GOOS,
+		"arch":     runtime.GOARCH,
+		"version":  common.VERSION,
+		"revision": common.REVISION,
+		"pid":      os.Getpid(),
+	}
+
+	logrus.WithFields(fields).Info("Runtime platform")
+
+	return nil
+}
diff --git a/helpers/cli/warn_on_bool.go b/app/warn_on_bool.go
similarity index 97%
rename from helpers/cli/warn_on_bool.go
rename to app/warn_on_bool.go
index 8539e8fe54b141bb7bb0c49bfa0a0ad449143449..c3490d0f8ec31dc8c40c884e40e7578d4479880b 100644
--- a/helpers/cli/warn_on_bool.go
+++ b/app/warn_on_bool.go
@@ -1,4 +1,4 @@
-package cli_helpers
+package app
 
 import (
 	"strings"
diff --git a/cmd/gitlab-runner-helper/main.go b/cmd/gitlab-runner-helper/main.go
index 40ab16bd98beed749814bc08329d47edcd9e1a8c..02b88c9eb300bba85fa024b13531ce6fa2db99be 100644
--- a/cmd/gitlab-runner-helper/main.go
+++ b/cmd/gitlab-runner-helper/main.go
@@ -1,49 +1,19 @@
 package main
 
 import (
-	"os"
-	"path"
-
 	"github.com/sirupsen/logrus"
-	"github.com/urfave/cli"
 
-	"gitlab.com/gitlab-org/gitlab-runner/common"
+	"gitlab.com/gitlab-org/gitlab-runner/app"
 	"gitlab.com/gitlab-org/gitlab-runner/log"
 
 	_ "gitlab.com/gitlab-org/gitlab-runner/commands/helpers"
 )
 
 func main() {
-	defer func() {
-		if r := recover(); r != nil {
-			// log panics forces exit
-			if _, ok := r.(*logrus.Entry); ok {
-				os.Exit(1)
-			}
-			panic(r)
-		}
-	}()
-
-	app := cli.NewApp()
-	app.Name = path.Base(os.Args[0])
-	app.Usage = "a GitLab Runner Helper"
-	app.Version = common.AppVersion.ShortLine()
-	cli.VersionPrinter = common.AppVersion.Printer
-	app.Authors = []cli.Author{
-		{
-			Name:  "GitLab Inc.",
-			Email: "support@gitlab.com",
-		},
-	}
-	app.Commands = common.GetCommands()
-	app.CommandNotFound = func(context *cli.Context, command string) {
-		logrus.Fatalln("Command", command, "not found")
-	}
+	defer app.Recover()
 
+	a := app.New("GitLab Runner Helper")
 	log.AddSecretsCleanupLogHook(logrus.StandardLogger())
-	log.ConfigureLogging(app)
 
-	if err := app.Run(os.Args); err != nil {
-		logrus.Fatal(err)
-	}
+	a.Run()
 }
diff --git a/cmd/gitlab-runner/main.go b/cmd/gitlab-runner/main.go
index 5bffd5bebe7051be1ba5850703f38d6550bdae1d..48dcbe3243af0ebd3788cdfb134bfeb46a3153f7 100644
--- a/cmd/gitlab-runner/main.go
+++ b/cmd/gitlab-runner/main.go
@@ -2,14 +2,8 @@ package main
 
 import (
 	"os"
-	"path"
 
-	"github.com/sirupsen/logrus"
-	"github.com/urfave/cli"
-
-	"gitlab.com/gitlab-org/gitlab-runner/common"
-	"gitlab.com/gitlab-org/gitlab-runner/helpers/cli"
-	"gitlab.com/gitlab-org/gitlab-runner/log"
+	"gitlab.com/gitlab-org/gitlab-runner/app"
 
 	_ "gitlab.com/gitlab-org/gitlab-runner/cache/gcs"
 	_ "gitlab.com/gitlab-org/gitlab-runner/cache/s3"
@@ -26,40 +20,15 @@ import (
 )
 
 func main() {
-	defer func() {
-		if r := recover(); r != nil {
-			// log panics forces exit
-			if _, ok := r.(*logrus.Entry); ok {
-				os.Exit(1)
-			}
-			panic(r)
-		}
-	}()
-
-	app := cli.NewApp()
-	app.Name = path.Base(os.Args[0])
-	app.Usage = "a GitLab Runner"
-	app.Version = common.AppVersion.ShortLine()
-	cli.VersionPrinter = common.AppVersion.Printer
-	app.Authors = []cli.Author{
-		{
-			Name:  "GitLab Inc.",
-			Email: "support@gitlab.com",
-		},
-	}
-	app.Commands = common.GetCommands()
-	app.CommandNotFound = func(context *cli.Context, command string) {
-		logrus.Fatalln("Command", command, "not found.")
-	}
-
-	cli_helpers.LogRuntimePlatform(app)
-	cli_helpers.SetupCPUProfile(app)
-	cli_helpers.FixHOME(app)
-	cli_helpers.WarnOnBool(os.Args)
+	defer app.Recover()
 
-	log.ConfigureLogging(app)
+	a := app.New("GitLab Runner")
+	a.AppendBeforeFunc(app.LogRuntimePlatform)
+	a.Extend(app.CPUProfileFlags)
+	a.AppendBeforeFunc(app.CPUProfileSetup)
+	a.AppendAfterFunc(app.CPUProfileTeardown)
+	a.AppendBeforeFunc(app.FixHOME)
+	app.WarnOnBool(os.Args)
 
-	if err := app.Run(os.Args); err != nil {
-		logrus.Fatal(err)
-	}
+	a.Run()
 }
diff --git a/helpers/cli/cpuprofile.go b/helpers/cli/cpuprofile.go
deleted file mode 100644
index e10f9c50725af73cb7194874d4c8d4ca7e692b3e..0000000000000000000000000000000000000000
--- a/helpers/cli/cpuprofile.go
+++ /dev/null
@@ -1,43 +0,0 @@
-package cli_helpers
-
-import (
-	"os"
-	"runtime/pprof"
-
-	"github.com/urfave/cli"
-)
-
-func SetupCPUProfile(app *cli.App) {
-	app.Flags = append(app.Flags, cli.StringFlag{
-		Name:   "cpuprofile",
-		Usage:  "write cpu profile to file",
-		EnvVar: "CPU_PROFILE",
-	})
-
-	appBefore := app.Before
-	appAfter := app.After
-
-	app.Before = func(c *cli.Context) error {
-		if cpuProfile := c.String("cpuprofile"); cpuProfile != "" {
-			f, err := os.Create(cpuProfile)
-			if err != nil {
-				return err
-			}
-			pprof.StartCPUProfile(f)
-		}
-
-		if appBefore != nil {
-			return appBefore(c)
-		}
-		return nil
-	}
-
-	app.After = func(c *cli.Context) error {
-		pprof.StopCPUProfile()
-
-		if appAfter != nil {
-			return appAfter(c)
-		}
-		return nil
-	}
-}
diff --git a/helpers/cli/fix_home.go b/helpers/cli/fix_home.go
deleted file mode 100644
index c327e18686e315f49e398cc0efac99e7e208b7b7..0000000000000000000000000000000000000000
--- a/helpers/cli/fix_home.go
+++ /dev/null
@@ -1,29 +0,0 @@
-package cli_helpers
-
-import (
-	"fmt"
-	"os"
-
-	"github.com/docker/docker/pkg/homedir"
-	"github.com/urfave/cli"
-)
-
-func FixHOME(app *cli.App) {
-	appBefore := app.Before
-
-	app.Before = func(c *cli.Context) error {
-		// Fix home
-		if key := homedir.Key(); os.Getenv(key) == "" {
-			value := homedir.Get()
-			if value == "" {
-				return fmt.Errorf("the %q is not set", key)
-			}
-			os.Setenv(key, value)
-		}
-
-		if appBefore != nil {
-			return appBefore(c)
-		}
-		return nil
-	}
-}
diff --git a/helpers/cli/runtime_platform.go b/helpers/cli/runtime_platform.go
deleted file mode 100644
index c82d13c676411433b02952adaa9b98161c8b3306..0000000000000000000000000000000000000000
--- a/helpers/cli/runtime_platform.go
+++ /dev/null
@@ -1,32 +0,0 @@
-package cli_helpers
-
-import (
-	"os"
-	"runtime"
-
-	"github.com/sirupsen/logrus"
-	"github.com/urfave/cli"
-
-	"gitlab.com/gitlab-org/gitlab-runner/common"
-)
-
-func LogRuntimePlatform(app *cli.App) {
-	appBefore := app.Before
-	app.Before = func(c *cli.Context) error {
-		fields := logrus.Fields{
-			"os":       runtime.GOOS,
-			"arch":     runtime.GOARCH,
-			"version":  common.VERSION,
-			"revision": common.REVISION,
-			"pid":      os.Getpid(),
-		}
-
-		logrus.WithFields(fields).Info("Runtime platform")
-
-		if appBefore != nil {
-			return appBefore(c)
-		}
-		return nil
-	}
-
-}
diff --git a/log/configuration.go b/log/configuration.go
index 03f187751f50c471c559a536eccc82fc294413ab..53e79595ee2bff6f13a488ea727d2d6b86d9f544 100644
--- a/log/configuration.go
+++ b/log/configuration.go
@@ -162,21 +162,17 @@ func Configuration() *Config {
 	return configuration
 }
 
-func ConfigureLogging(app *cli.App) {
+func AddFlags(app *cli.App) {
 	app.Flags = append(app.Flags, logFlags...)
+}
 
-	appBefore := app.Before
-	app.Before = func(cliCtx *cli.Context) error {
-		Configuration().logger.SetOutput(os.Stderr)
-
-		err := Configuration().handleCliCtx(cliCtx)
-		if err != nil {
-			logrus.WithError(err).Fatal("Error while setting up logging configuration")
-		}
+func ConfigureLogging(cliCtx *cli.Context) error {
+	Configuration().logger.SetOutput(os.Stderr)
 
-		if appBefore != nil {
-			return appBefore(cliCtx)
-		}
-		return nil
+	err := Configuration().handleCliCtx(cliCtx)
+	if err != nil {
+		return fmt.Errorf("error while setting up logging configuration: %v", err)
 	}
+
+	return nil
 }
diff --git a/log/configuration_test.go b/log/configuration_test.go
index 1a0bf6bd8af4d74bcee834bd32ed55eb88a9d408..ef85d6db40f94a94e9d4dedd3e33774902feded4 100644
--- a/log/configuration_test.go
+++ b/log/configuration_test.go
@@ -6,10 +6,7 @@ import (
 	"github.com/sirupsen/logrus"
 	"github.com/sirupsen/logrus/hooks/test"
 	"github.com/stretchr/testify/assert"
-	"github.com/stretchr/testify/require"
 	"github.com/urfave/cli"
-
-	"gitlab.com/gitlab-org/gitlab-runner/helpers"
 )
 
 func prepareFakeConfiguration(logger *logrus.Logger) func() {
@@ -22,7 +19,7 @@ func prepareFakeConfiguration(logger *logrus.Logger) func() {
 	}
 }
 
-func testCommandRun(args ...string) {
+func testCommandRun(t *testing.T, args ...string) error {
 	app := cli.NewApp()
 	app.Commands = []cli.Command{
 		{
@@ -31,12 +28,13 @@ func testCommandRun(args ...string) {
 		},
 	}
 
-	ConfigureLogging(app)
+	AddFlags(app)
+	app.Before = ConfigureLogging
 
 	args = append([]string{"binary"}, args...)
 	args = append(args, "logtest")
 
-	app.Run(args)
+	return app.Run(args)
 }
 
 type handleCliCtxTestCase struct {
@@ -96,46 +94,25 @@ func TestHandleCliCtx(t *testing.T) {
 			logger, _ := test.NewNullLogger()
 
 			defer prepareFakeConfiguration(logger)()
-			defer helpers.MakeFatalToPanic()()
-
-			testFunc := func() {
-				testCommandRun(testCase.args...)
-				if testCase.expectedError == "" {
-					assert.Equal(t, testCase.expectedLevel, Configuration().level)
-					assert.Equal(t, testCase.expectedFormatter, Configuration().format)
-					assert.Equal(t, testCase.expectedLevelSetWithCli, Configuration().IsLevelSetWithCli())
-					assert.Equal(t, testCase.expectedFormatSetWithCli, Configuration().IsFormatSetWithCli())
-
-					if testCase.goroutinesDumpStopChExists {
-						assert.NotNil(t, Configuration().goroutinesDumpStopCh)
-					} else {
-						assert.Nil(t, Configuration().goroutinesDumpStopCh)
-					}
-				}
-			}
-
-			if testCase.expectedError != "" {
-				var message *logrus.Entry
-				var ok bool
 
-				func() {
-					defer func() {
-						message, ok = recover().(*logrus.Entry)
-					}()
+			err := testCommandRun(t, testCase.args...)
 
-					testFunc()
-				}()
+			if testCase.expectedError == "" {
+				assert.NoError(t, err)
 
-				require.True(t, ok)
-
-				panicMessage, err := message.String()
-				require.NoError(t, err)
-
-				assert.Contains(t, panicMessage, "Error while setting up logging configuration")
-				assert.Contains(t, panicMessage, testCase.expectedError)
+				assert.Equal(t, testCase.expectedLevel, Configuration().level)
+				assert.Equal(t, testCase.expectedFormatter, Configuration().format)
+				assert.Equal(t, testCase.expectedLevelSetWithCli, Configuration().IsLevelSetWithCli())
+				assert.Equal(t, testCase.expectedFormatSetWithCli, Configuration().IsFormatSetWithCli())
 
+				if testCase.goroutinesDumpStopChExists {
+					assert.NotNil(t, Configuration().goroutinesDumpStopCh)
+				} else {
+					assert.Nil(t, Configuration().goroutinesDumpStopCh)
+				}
 			} else {
-				assert.NotPanics(t, testFunc)
+				assert.Error(t, err)
+				assert.Contains(t, err.Error(), "error while setting up logging configuration")
 			}
 		})
 	}