diff --git a/cmd/promtail/main.go b/cmd/promtail/main.go
index ed5844e63f2114f6dd343d1c646314e327a13eda..b68c8249a6e36be596554b9164148c0ba5deccd0 100644
--- a/cmd/promtail/main.go
+++ b/cmd/promtail/main.go
@@ -2,27 +2,27 @@ package main
 
 import (
 	"flag"
-	"os"
 
-	"github.com/cortexproject/cortex/pkg/util"
 	"github.com/go-kit/kit/log/level"
 	"github.com/prometheus/common/model"
+
+	"github.com/cortexproject/cortex/pkg/util"
+	"github.com/cortexproject/cortex/pkg/util/flagext"
 	"github.com/weaveworks/common/server"
 
-	"github.com/grafana/tempo/pkg/flagext"
 	"github.com/grafana/tempo/pkg/promtail"
 )
 
 func main() {
 	var (
-		flagset         = flag.NewFlagSet("", flag.ExitOnError)
-		configFile      = flagset.String("config.file", "promtail.yml", "The config file.")
+		configFile      string
 		serverConfig    server.Config
 		clientConfig    promtail.ClientConfig
 		positionsConfig promtail.PositionsConfig
 	)
-	flagext.RegisterConfigs(flagset, &serverConfig, &clientConfig, &positionsConfig)
-	flagset.Parse(os.Args[1:])
+	flag.StringVar(&configFile, "config.file", "promtail.yml", "The config file.")
+	flagext.RegisterFlags(&serverConfig, &clientConfig, &positionsConfig)
+	flag.Parse()
 
 	util.InitLogger(&serverConfig)
 
@@ -39,7 +39,7 @@ func main() {
 		return
 	}
 
-	cfg, err := promtail.LoadConfig(*configFile)
+	cfg, err := promtail.LoadConfig(configFile)
 	if err != nil {
 		level.Error(util.Logger).Log("msg", "Failed to load config", "error", err)
 		return
diff --git a/cmd/tempo/main.go b/cmd/tempo/main.go
index a72e97b642026936536d181564a89cb624e5bf4c..7fa1cef9c6f94194c14c802e6cdabb0b17ef80dc 100644
--- a/cmd/tempo/main.go
+++ b/cmd/tempo/main.go
@@ -3,22 +3,26 @@ package main
 import (
 	"flag"
 	"fmt"
+	"io/ioutil"
 	"net/http"
 	"os"
 	"strings"
 
-	"github.com/cortexproject/cortex/pkg/ring"
-	"github.com/cortexproject/cortex/pkg/util"
+	"github.com/go-kit/kit/log/level"
 	"github.com/opentracing-contrib/go-stdlib/nethttp"
 	opentracing "github.com/opentracing/opentracing-go"
-	log "github.com/sirupsen/logrus"
-	"github.com/weaveworks/common/middleware"
-	"github.com/weaveworks/common/server"
+	"github.com/pkg/errors"
 	"google.golang.org/grpc"
 	"google.golang.org/grpc/health/grpc_health_v1"
+	"gopkg.in/yaml.v2"
+
+	"github.com/cortexproject/cortex/pkg/ring"
+	"github.com/cortexproject/cortex/pkg/util"
+	"github.com/cortexproject/cortex/pkg/util/flagext"
+	"github.com/weaveworks/common/middleware"
+	"github.com/weaveworks/common/server"
 
 	"github.com/grafana/tempo/pkg/distributor"
-	"github.com/grafana/tempo/pkg/flagext"
 	"github.com/grafana/tempo/pkg/ingester"
 	"github.com/grafana/tempo/pkg/ingester/client"
 	"github.com/grafana/tempo/pkg/logproto"
@@ -26,24 +30,27 @@ import (
 )
 
 type config struct {
-	serverConfig         server.Config
-	distributorConfig    distributor.Config
-	ingesterConfig       ingester.Config
-	querierConfig        querier.Config
-	ingesterClientConfig client.Config
+	Server         server.Config      `yaml:"server,omitempty"`
+	Distributor    distributor.Config `yaml:"distributor,omitempty"`
+	Querier        querier.Config     `yaml:"querier,omitempty"`
+	IngesterClient client.Config      `yaml:"ingester_client,omitempty"`
+	Ingester       ingester.Config    `yaml:"ingester,omitempty"`
 }
 
 func (c *config) RegisterFlags(f *flag.FlagSet) {
-	c.serverConfig.MetricsNamespace = "tempo"
-	c.serverConfig.GRPCMiddleware = []grpc.UnaryServerInterceptor{
+	c.Server.MetricsNamespace = "tempo"
+	c.Server.GRPCMiddleware = []grpc.UnaryServerInterceptor{
 		middleware.ServerUserHeaderInterceptor,
 	}
-	c.serverConfig.GRPCStreamMiddleware = []grpc.StreamServerInterceptor{
+	c.Server.GRPCStreamMiddleware = []grpc.StreamServerInterceptor{
 		middleware.StreamServerUserHeaderInterceptor,
 	}
 
-	flagext.RegisterConfigs(f, &c.serverConfig, &c.distributorConfig,
-		&c.ingesterConfig, &c.querierConfig, &c.ingesterClientConfig)
+	c.Server.RegisterFlags(f)
+	c.Distributor.RegisterFlags(f)
+	c.Querier.RegisterFlags(f)
+	c.IngesterClient.RegisterFlags(f)
+	c.Ingester.RegisterFlags(f)
 }
 
 type Tempo struct {
@@ -118,7 +125,7 @@ type module struct {
 var modules = map[moduleName]module{
 	Server: module{
 		init: func(t *Tempo, cfg *config) (err error) {
-			t.server, err = server.New(cfg.serverConfig)
+			t.server, err = server.New(cfg.Server)
 			return
 		},
 	},
@@ -126,7 +133,7 @@ var modules = map[moduleName]module{
 	Ring: module{
 		deps: []moduleName{Server},
 		init: func(t *Tempo, cfg *config) (err error) {
-			t.ring, err = ring.New(cfg.ingesterConfig.LifecyclerConfig.RingConfig)
+			t.ring, err = ring.New(cfg.Ingester.LifecyclerConfig.RingConfig)
 			if err != nil {
 				return
 			}
@@ -138,7 +145,7 @@ var modules = map[moduleName]module{
 	Distributor: module{
 		deps: []moduleName{Ring, Server},
 		init: func(t *Tempo, cfg *config) (err error) {
-			t.distributor, err = distributor.New(cfg.distributorConfig, cfg.ingesterClientConfig, t.ring)
+			t.distributor, err = distributor.New(cfg.Distributor, cfg.IngesterClient, t.ring)
 			if err != nil {
 				return
 			}
@@ -160,8 +167,8 @@ var modules = map[moduleName]module{
 	Ingester: module{
 		deps: []moduleName{Server},
 		init: func(t *Tempo, cfg *config) (err error) {
-			cfg.ingesterConfig.LifecyclerConfig.ListenPort = &cfg.serverConfig.GRPCListenPort
-			t.ingester, err = ingester.New(cfg.ingesterConfig)
+			cfg.Ingester.LifecyclerConfig.ListenPort = &cfg.Server.GRPCListenPort
+			t.ingester, err = ingester.New(cfg.Ingester)
 			if err != nil {
 				return
 			}
@@ -180,7 +187,7 @@ var modules = map[moduleName]module{
 	Querier: module{
 		deps: []moduleName{Ring, Server},
 		init: func(t *Tempo, cfg *config) (err error) {
-			t.querier, err = querier.New(cfg.querierConfig, cfg.ingesterClientConfig, t.ring)
+			t.querier, err = querier.New(cfg.Querier, cfg.IngesterClient, t.ring)
 			if err != nil {
 				return
 			}
@@ -215,20 +222,22 @@ var (
 	inited = map[moduleName]struct{}{}
 )
 
-func initModule(m moduleName) {
+func initModule(m moduleName) error {
 	if _, ok := inited[m]; ok {
-		return
+		return nil
 	}
 
 	for _, dep := range modules[m].deps {
 		initModule(dep)
 	}
 
+	level.Info(util.Logger).Log("msg", "initialising", "module", m)
 	if err := modules[m].init(&tempo, &cfg); err != nil {
-		log.Fatalf("Error initializing %s: %v", m, err)
+		return errors.Wrap(err, fmt.Sprintf("error initialising module: %s", m))
 	}
 
 	inited[m] = struct{}{}
+	return nil
 }
 
 func stopModule(m moduleName) {
@@ -241,21 +250,55 @@ func stopModule(m moduleName) {
 		stopModule(dep)
 	}
 
-	modules[m].stop(&tempo)
+	if modules[m].stop != nil {
+		level.Info(util.Logger).Log("msg", "stopping", "module", m)
+		modules[m].stop(&tempo)
+	}
+}
+
+func readConfig(filename string) error {
+	f, err := os.Open(filename)
+	if err != nil {
+		return errors.Wrap(err, "error opening config file")
+	}
+	defer f.Close()
+
+	buf, err := ioutil.ReadAll(f)
+	if err != nil {
+		return errors.Wrap(err, "Error reading config file: %v")
+	}
+
+	if err := yaml.Unmarshal(buf, &cfg); err != nil {
+		return errors.Wrap(err, "Error reading config file: %v")
+	}
+	return nil
 }
 
 func main() {
-	flagset := flag.NewFlagSet("", flag.ExitOnError)
-	target := All
-	flagset.Var(&target, "target", "target module (default All)")
-	flagext.RegisterConfigs(flagset, &cfg)
-	flagset.Parse(os.Args[1:])
+	var (
+		target     = All
+		configFile = ""
+	)
+	flag.Var(&target, "target", "target module (default All)")
+	flag.StringVar(&configFile, "config.file", "", "Configuration file to load.")
+	flagext.RegisterFlags(&cfg)
+	flag.Parse()
+
+	util.InitLogger(&cfg.Server)
+
+	if configFile != "" {
+		if err := readConfig(configFile); err != nil {
+			level.Error(util.Logger).Log("msg", "error loading config", "filename", configFile, "err", err)
+			os.Exit(1)
+		}
+	}
 
-	util.InitLogger(&cfg.serverConfig)
+	if err := initModule(target); err != nil {
+		level.Error(util.Logger).Log("msg", "error initialising module", "err", err)
+		os.Exit(1)
+	}
 
-	initModule(target)
 	tempo.server.Run()
-
 	tempo.server.Shutdown()
 	stopModule(target)
 }
diff --git a/docs/local.yaml b/docs/local.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..5e0fe0189b4f0dfb2a0f65c5bb5f1e7893eb334b
--- /dev/null
+++ b/docs/local.yaml
@@ -0,0 +1,5 @@
+ingester:
+  lifecycler:
+    ring:
+      store: inmemory
+      replication_factor: 1
diff --git a/pkg/flagext/flagext.go b/pkg/flagext/flagext.go
deleted file mode 100644
index cff3e76bf3201a9d8e4d6a00f85c821e566ce245..0000000000000000000000000000000000000000
--- a/pkg/flagext/flagext.go
+++ /dev/null
@@ -1,18 +0,0 @@
-package flagext
-
-import "flag"
-
-type Registerable interface {
-	RegisterFlags(*flag.FlagSet)
-}
-
-func RegisterConfigs(flagset *flag.FlagSet, cfgs ...Registerable) {
-	for _, cfg := range cfgs {
-		cfg.RegisterFlags(flagset)
-	}
-}
-
-func Var(flagset *flag.FlagSet, v flag.Value, name, def, help string) {
-	v.Set(def)
-	flagset.Var(v, name, help)
-}
diff --git a/pkg/flagext/url_flag.go b/pkg/flagext/url_flag.go
deleted file mode 100644
index b965dc1e004591ea5adbde6c258b3b9f7d1867b3..0000000000000000000000000000000000000000
--- a/pkg/flagext/url_flag.go
+++ /dev/null
@@ -1,20 +0,0 @@
-package flagext
-
-import "net/url"
-
-type URL struct {
-	*url.URL
-}
-
-func (u *URL) String() string {
-	if u.URL != nil {
-		return u.URL.String()
-	}
-	return ""
-}
-
-func (u *URL) Set(value string) error {
-	var err error
-	u.URL, err = url.Parse(value)
-	return err
-}
diff --git a/pkg/ingester/client/client.go b/pkg/ingester/client/client.go
index 90437134566522e2b38f72ea40a4eb698d0a83be..3dd0d2bb2fb179ab0d71c82403bffb25dbe07574 100644
--- a/pkg/ingester/client/client.go
+++ b/pkg/ingester/client/client.go
@@ -16,9 +16,9 @@ import (
 )
 
 type Config struct {
-	PoolConfig     cortex_client.PoolConfig
-	MaxRecvMsgSize int
-	RemoteTimeout  time.Duration
+	PoolConfig     cortex_client.PoolConfig `yaml:"pool_config,omitempty"`
+	MaxRecvMsgSize int                      `yaml:"max_recv_msg_size,omitempty"`
+	RemoteTimeout  time.Duration            `yaml:"remote_timeout,omitempty"`
 }
 
 func (cfg *Config) RegisterFlags(f *flag.FlagSet) {
diff --git a/pkg/ingester/ingester.go b/pkg/ingester/ingester.go
index e9daf1963fd0b5dc9d596d9a711c155a4d1e319a..83801ae2886fc7e85d2b8a51994ef1846b1b022d 100644
--- a/pkg/ingester/ingester.go
+++ b/pkg/ingester/ingester.go
@@ -14,7 +14,7 @@ import (
 )
 
 type Config struct {
-	LifecyclerConfig ring.LifecyclerConfig
+	LifecyclerConfig ring.LifecyclerConfig `yaml:"lifecycler,omitempty"`
 }
 
 func (cfg *Config) RegisterFlags(f *flag.FlagSet) {
diff --git a/pkg/promtail/client.go b/pkg/promtail/client.go
index 1e519c0ddff1c5d413e9bd66d04c4c60ea754472..25300ea65ed3af7e1c4b1b2ba904abf04127f6b3 100644
--- a/pkg/promtail/client.go
+++ b/pkg/promtail/client.go
@@ -15,7 +15,8 @@ import (
 	"github.com/prometheus/common/model"
 	log "github.com/sirupsen/logrus"
 
-	"github.com/grafana/tempo/pkg/flagext"
+	"github.com/cortexproject/cortex/pkg/util/flagext"
+
 	"github.com/grafana/tempo/pkg/logproto"
 )
 
@@ -40,7 +41,7 @@ func init() {
 }
 
 type ClientConfig struct {
-	URL       flagext.URL
+	URL       flagext.URLValue
 	BatchWait time.Duration
 	BatchSize int
 }