diff --git a/Makefile b/Makefile
index 6190379bdc3152bc521c2f20c4d9eb9e6d079c5b..4b4ec663db8e5e18cf06e0bf73647bcdba329e13 100644
--- a/Makefile
+++ b/Makefile
@@ -3,7 +3,7 @@ ROOT_DIR := $(shell pwd)
 
 PROJECT_NAME := hive
 PROJECT_DOCKER_TAG := steemit/$(PROJECT_NAME)
-PROJECT_DOCKER_RUN_ARGS := --link mysql:mysql
+PROJECT_DOCKER_RUN_ARGS := --link db:db
 
 default: build
 
@@ -18,9 +18,11 @@ run:
 compose:
 	docker-compose up -d
 
-mysql:
-	docker run -d --name steemit_mysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=root_password -e MYSQL_DATABASE=testdb mysql
+db:
+	docker run -d --name hive_db -p 5432:5432 -e POSTGRES_PASSWORD=root_password -e POSTGRES_DATABASE=hivepy postgres
 
+mysql:
+	docker run --env DATABASE_URL=mysql://root:root_password@mysql:3306/testdb -p 4000:8080 hive
 
 serve-local:
 	pipenv run python hive/server/serve.py --port 8080 --database_url='mysql://root:root_password@127.0.0.1:3306/testdb'
diff --git a/Pipfile b/Pipfile
index c224e9810d40db28740bab9bff1a4d742a2b6c6c..e4754682b599679542ec5b30d96c7f465ae87150 100644
--- a/Pipfile
+++ b/Pipfile
@@ -6,8 +6,6 @@ verify_ssl = true
 
 sqlalchemy = "*"
 mysqlclient = "*"
-click = "*"
-"click-spinner" = "*"
 funcy = "*"
 toolz = "*"
 maya = "*"
@@ -32,4 +30,4 @@ recommonmark = "*"
 "sphinxcontrib-restbuilder" = "*"
 yapf = "*"
 sphinx = "*"
-"autopep8" = "*"
\ No newline at end of file
+"autopep8" = "*"
diff --git a/hive/conf.py b/hive/conf.py
index c0aefc58fdf340d25cbded15170a313242de797d..81e1d3dbb843276e7913ede0593a1a0d7e936fbb 100644
--- a/hive/conf.py
+++ b/hive/conf.py
@@ -11,8 +11,8 @@ class Conf():
         #pylint: disable=invalid-name,line-too-long
         p = configargparse.get_arg_parser(default_config_files=['./hive.conf'])
 
-        # runmodes: sync, status
-        p.add('--mode', default='sync')
+        # runmodes: sync, server, status
+        p.add('mode', nargs='*', default=['sync'])
 
         # common
         p.add('--database-url', env_var='DATABASE_URL', required=True, help='database connection url', default='postgresql://user:pass@localhost:5432/hive')
@@ -25,10 +25,14 @@ class Conf():
         p.add('--trail-blocks', type=int, env_var='TRAIL_BLOCKS', default=2)
 
         # specific to API server
-        p.add('--port', type=int, env_var='PORT', default=8080)
+        p.add('--http-server-port', type=int, env_var='HTTP_SERVER_PORT', default=8080)
 
         cls._args = p.parse_args()
 
+        if cls.get('log_level') == 'DEBUG':
+            print(cls._args)
+            print(p.format_values())
+
     @classmethod
     def args(cls):
         return cls._args
diff --git a/hive/indexer/core.py b/hive/indexer/core.py
index fd8df265f33531513bbbb7d045be62ccb243cf7e..aa1e411e21b920b25c6e19065c43b8147fc59256 100755
--- a/hive/indexer/core.py
+++ b/hive/indexer/core.py
@@ -11,7 +11,7 @@ from hive.indexer.cached_post import CachedPost
 log = logging.getLogger(__name__)
 
 
-def run():
+def run_sync():
     print("[HIVE] Welcome to hivemind")
 
     # make sure db schema is up to date, perform checks
@@ -46,7 +46,4 @@ def run():
 
 if __name__ == '__main__':
     Conf.init_argparse()
-    if Conf.get('mode') == 'status':
-        print(DbState.status())
-    else:
-        run()
+    run_sync()
diff --git a/hive/server/serve.py b/hive/server/serve.py
index eff84f4ec91cbcf49c0151cc1cf88c725a61b9ae..a89ff74b75de70ec649acf105b2cb908a123ecc9 100644
--- a/hive/server/serve.py
+++ b/hive/server/serve.py
@@ -14,127 +14,139 @@ from hive.conf import Conf
 from hive.server import condenser_api
 from hive.server import hive_api
 
-Conf.init_argparse()
-log_level = Conf.log_level()
-
-config.debug = (log_level == logging.DEBUG)
-logging.basicConfig(level=log_level)
-logger = logging.getLogger(__name__)
-logging.getLogger('jsonrpcserver.dispatcher.response').setLevel(log_level)
-
-
-hive_methods = (
-    hive_api.db_head_state,
-    hive_api.get_followers,
-    hive_api.get_following,
-    hive_api.get_follow_count,
-    hive_api.get_user_feed,
-    hive_api.get_blog_feed,
-    hive_api.get_discussions_by_sort_and_tag,
-    hive_api.get_related_posts,
-    hive_api.payouts_total,
-    hive_api.payouts_last_24h
-)
-
-condenser_methods = (
-    condenser_api.call,
-    condenser_api.get_followers,
-    condenser_api.get_following,
-    condenser_api.get_follow_count,
-    condenser_api.get_discussions_by_trending,
-    condenser_api.get_discussions_by_hot,
-    condenser_api.get_discussions_by_promoted,
-    condenser_api.get_discussions_by_created,
-    condenser_api.get_discussions_by_blog,
-    condenser_api.get_discussions_by_feed,
-    condenser_api.get_discussions_by_comments,
-    condenser_api.get_replies_by_last_update,
-    condenser_api.get_content,
-    condenser_api.get_content_replies,
-    condenser_api.get_state
-)
-
-# Register hive_api methods and (appbase) condenser_api methods
-methods = AsyncMethods()
-for m in hive_methods:
-    methods.add(m)
-    methods.add(m, 'hive_api.' + m.__name__)  # TODO: temp, for testing jussi-style path without jussi
-for m in condenser_methods:
-    # note: unclear if appbase expects condenser_api.call or call.condenser_api
-    methods.add(m, 'condenser_api.' + m.__name__)
-    methods.add(m, 'hive_api.condenser_api.' + m.__name__) # TODO: temp, for testing jussi-style path without jussi
-
-# Register non-appbase condenser_api endpoint (remove after appbase in prod)
-non_appbase_methods = AsyncMethods()
-non_appbase_methods.add(condenser_api.call, 'condenser_api.non_appb.call')
-non_appbase_methods.add(condenser_api.call, 'hive_api.condenser_api.non_appb.call') # TODO: temp, for testing jussi-style path without jussi
-for m in condenser_methods:
-    non_appbase_methods.add(m)
-
-app = web.Application()
-app['config'] = dict()
-app['config']['hive.MAX_DB_ROW_RESULTS'] = 100000
-app['config']['hive.DB_QUERY_LIMIT'] = app['config']['hive.MAX_DB_ROW_RESULTS'] + 1
-app['config']['hive.logger'] = logger
-
-async def init_db(app):
-    args = app['config']['args']
-    db = make_url(args.database_url)
-    engine = await create_engine(user=db.username,
-                                 database=db.database,
-                                 password=db.password,
-                                 host=db.host,
-                                 port=db.port,
-                                 **db.query)
-    app['db'] = engine
-
-async def close_db(app):
-    app['db'].close()
-    await app['db'].wait_closed()
-
-
-# Non JSON-RPC routes
-# -------------------
-async def health(request):
-    state = await hive_api.db_head_state()
-    max_head_age = (Conf.get('trail_blocks') + 1) * 3
-
-    if state['db_head_age'] > max_head_age:
-        status = 500
-        result = 'head block age (%s) > max (%s); head block num: %s' % (
-            state['db_head_age'], max_head_age, state['db_head_block'])
-    else:
-        status = 200
-        result = 'head block age is %d, head block num is %d' % (
-            state['db_head_age'], state['db_head_block'])
-
-    return web.json_response(status=status, data=dict(
-        state=state,
-        result=result,
-        status='OK' if status == 200 else 'WARN',
-        source_commit=os.environ.get('SOURCE_COMMIT'),
-        schema_hash=os.environ.get('SCHEMA_HASH'),
-        docker_tag=os.environ.get('DOCKER_TAG'),
-        timestamp=datetime.utcnow().isoformat()))
-
-async def jsonrpc_handler(request):
-    request = await request.text()
-    response = await methods.dispatch(request)
-    return web.json_response(response, status=200, headers={'Access-Control-Allow-Origin': '*'})
-
-async def non_appbase_handler(request):
-    request = await request.text()
-    response = await non_appbase_methods.dispatch(request)
-    return web.json_response(response, status=200, headers={'Access-Control-Allow-Origin': '*'})
-
-
-app.on_startup.append(init_db)
-app.on_cleanup.append(close_db)
-app.router.add_get('/health', health)
-app.router.add_post('/', jsonrpc_handler)
-app.router.add_post('/legacy', non_appbase_handler)
+
+def run_server():
+
+    log_level = Conf.log_level()
+
+    config.debug = (log_level == logging.DEBUG)
+    logging.basicConfig(level=log_level)
+    logger = logging.getLogger(__name__)
+    logging.getLogger('jsonrpcserver.dispatcher.response').setLevel(log_level)
+
+    hive_methods = (
+        hive_api.db_head_state,
+        hive_api.get_followers,
+        hive_api.get_following,
+        hive_api.get_follow_count,
+        hive_api.get_user_feed,
+        hive_api.get_blog_feed,
+        hive_api.get_discussions_by_sort_and_tag,
+        hive_api.get_related_posts,
+        hive_api.payouts_total,
+        hive_api.payouts_last_24h
+    )
+
+    condenser_methods = (
+        condenser_api.call,
+        condenser_api.get_followers,
+        condenser_api.get_following,
+        condenser_api.get_follow_count,
+        condenser_api.get_discussions_by_trending,
+        condenser_api.get_discussions_by_hot,
+        condenser_api.get_discussions_by_promoted,
+        condenser_api.get_discussions_by_created,
+        condenser_api.get_discussions_by_blog,
+        condenser_api.get_discussions_by_feed,
+        condenser_api.get_discussions_by_comments,
+        condenser_api.get_replies_by_last_update,
+        condenser_api.get_content,
+        condenser_api.get_content_replies,
+        condenser_api.get_state
+    )
+
+    # Register hive_api methods and (appbase) condenser_api methods
+    methods = AsyncMethods()
+    for method in hive_methods:
+        methods.add(method)
+
+        # TODO: temp, for testing jussi-style path without jussi
+        methods.add(method, 'hive_api.' + method.__name__)
+
+    for method in condenser_methods:
+        # note: unclear if appbase expects condenser_api.call or call.condenser_api
+        methods.add(method, 'condenser_api.' + method.__name__)
+
+        # TODO: temp, for testing jussi-style path without jussi
+        methods.add(method, 'hive_api.condenser_api.' + method.__name__)
+
+    # Register non-appbase condenser_api endpoint (remove after appbase in prod)
+    non_appbase_methods = AsyncMethods()
+    for method in condenser_methods:
+        non_appbase_methods.add(method)
+
+    # TODO: temp, for testing jussi-style path without jussi
+    non_appbase_methods.add(condenser_api.call, 'condenser_api.non_appb.call')
+    non_appbase_methods.add(condenser_api.call, 'hive_api.condenser_api.non_appb.call')
+    # -----
+
+    app = web.Application()
+    app['config'] = dict()
+    app['config']['args'] = Conf.args()
+    app['config']['hive.MAX_DB_ROW_RESULTS'] = 100000
+    app['config']['hive.DB_QUERY_LIMIT'] = app['config']['hive.MAX_DB_ROW_RESULTS'] + 1
+    app['config']['hive.logger'] = logger
+
+
+    async def init_db(app):
+        args = app['config']['args']
+        db = make_url(args.database_url)
+        engine = await create_engine(user=db.username,
+                                     database=db.database,
+                                     password=db.password,
+                                     host=db.host,
+                                     port=db.port,
+                                     **db.query)
+        app['db'] = engine
+
+    async def close_db(app):
+        app['db'].close()
+        await app['db'].wait_closed()
+
+    app.on_startup.append(init_db)
+    app.on_cleanup.append(close_db)
+
+
+    async def health(request):
+        #pylint: disable=unused-argument
+        state = await hive_api.db_head_state()
+        max_head_age = (Conf.get('trail_blocks') + 1) * 3
+
+        if state['db_head_age'] > max_head_age:
+            status = 500
+            result = 'head block age (%s) > max (%s); head block num: %s' % (
+                state['db_head_age'], max_head_age, state['db_head_block'])
+        else:
+            status = 200
+            result = 'head block age is %d, head block num is %d' % (
+                state['db_head_age'], state['db_head_block'])
+
+        return web.json_response(status=status, data=dict(
+            state=state,
+            result=result,
+            status='OK' if status == 200 else 'WARN',
+            source_commit=os.environ.get('SOURCE_COMMIT'),
+            schema_hash=os.environ.get('SCHEMA_HASH'),
+            docker_tag=os.environ.get('DOCKER_TAG'),
+            timestamp=datetime.utcnow().isoformat()))
+
+    async def jsonrpc_handler(request):
+        request = await request.text()
+        response = await methods.dispatch(request)
+        return web.json_response(response, status=200, headers={'Access-Control-Allow-Origin': '*'})
+
+    async def non_appbase_handler(request):
+        request = await request.text()
+        response = await non_appbase_methods.dispatch(request)
+        return web.json_response(response, status=200, headers={'Access-Control-Allow-Origin': '*'})
+
+    app.router.add_get('/health', health)
+    app.router.add_post('/', jsonrpc_handler)
+    app.router.add_post('/legacy', non_appbase_handler)
+
+    web.run_app(app, port=app['config']['args'].http_server_port)
 
 
 if __name__ == '__main__':
-    app['config']['args'] = Conf.args()
-    web.run_app(app, port=app['config']['args'].port)
+    Conf.init_argparse()
+    run_server()
diff --git a/run.py b/run.py
new file mode 100755
index 0000000000000000000000000000000000000000..b52ac1554b379096e67f298162e224d69f4aa62d
--- /dev/null
+++ b/run.py
@@ -0,0 +1,25 @@
+#!/usr/local/bin/python3
+
+from hive.conf import Conf
+from hive.db.db_state import DbState
+from hive.indexer.core import run_sync
+from hive.server.serve import run_server
+
+def run():
+    Conf.init_argparse()
+    mode = '/'.join(Conf.get('mode'))
+
+    if mode == 'server':
+        run_server()
+
+    elif mode == 'sync':
+        run_sync()
+
+    elif mode == 'status':
+        print(DbState.status())
+
+    else:
+        raise Exception("unknown run mode %s" % mode)
+
+if __name__ == '__main__':
+    run()
diff --git a/service/hive-indexer/run b/service/hive-indexer/run
index c54c312c7f9acc179d341d5cc5525a2342a25ddd..748257ea4ee542e0e23781e97c00ddd547547885 100755
--- a/service/hive-indexer/run
+++ b/service/hive-indexer/run
@@ -2,4 +2,4 @@
 
 POPULATE_CMD="$(which hive)"
 
-exec "${POPULATE_CMD}" indexer run
+exec "${POPULATE_CMD}" sync
diff --git a/service/hive-web/run b/service/hive-web/run
index 44891287b983a1b5c2ecd3aa7968e20c4594e1e5..ad077d1f5863bed219a96660155a484ea1c6b811 100755
--- a/service/hive-web/run
+++ b/service/hive-web/run
@@ -2,4 +2,4 @@
 
 POPULATE_CMD="$(which hive)"
 
-python3 /app/hive/server/serve.py --database-url="${DATABASE_URL}" --port="${HTTP_SERVER_PORT}"
+python3 /app/hive/run.py server --database-url="${DATABASE_URL}" --http-server-port="${HTTP_SERVER_PORT}"
diff --git a/setup.py b/setup.py
index 37865809264026aff95a0e3b1c46a4945f3e5d59..82770c8bcf6665b57c0f13a75483a3ec9cc7f1aa 100644
--- a/setup.py
+++ b/setup.py
@@ -30,19 +30,17 @@ setup(
         'aiohttp',
         'certifi',
         'sqlalchemy',
-        'click',
         'funcy',
         'toolz',
         'maya',
         'ujson',
         'urllib3',
-        'PrettyTable',
         'psycopg2',
         'aiocache',
         'configargparse',
     ],
     entry_points={
         'console_scripts': [
-            'hive=hive.cli:cli',
+            'hive=run:run',
         ]
     })