diff --git a/src/dockerl_steps.erl b/src/dockerl_steps.erl
new file mode 100644
index 0000000000000000000000000000000000000000..d4162c3e5a02e1dcea0e2c8bb0439a7491998d33
--- /dev/null
+++ b/src/dockerl_steps.erl
@@ -0,0 +1,87 @@
+-module(dockerl_steps).
+-author("erikh").
+
+%% API
+-export([
+    setup_feature/2,
+    teardown_feature/1,
+    given/2
+]).
+
+%% Utility
+-export([
+    get_port/3
+]).
+
+setup_feature(_Tokens, State) ->
+    application:ensure_all_started(hackney),
+    {ok, Pid} = dockerl:start_link(socket, <<"/var/run/docker.sock">>),
+    {ok, State#{{?MODULE, pid} => Pid, {?MODULE, containers} => []}}.
+
+teardown_feature(State = #{{?MODULE, pid} := Pid, {?MODULE, containers} := Containers}) ->
+    lists:foreach(
+        fun({Name, Id}) ->
+            ok = dockerl:stop_container(Pid, Id),
+            lager:notice("Docker container ~s stopped", [Name]),
+            ok = dockerl:remove_container(Pid, Id),
+            lager:notice("Docker container ~s removed", [Name])
+        end, Containers),
+    {ok, State}.
+
+given([<<"a">>, <<"docker">>, <<"container">>, <<"named">>, Name, <<"running">>, Image, <<"with">>, <<"commands:">>, {docstring, Args}], State = #{{?MODULE, pid} := Pid, {?MODULE, containers} := Containers}) ->
+    {ok, _} = dockerl:pull_image(Pid, Image),
+    {ok, Id} = dockerl:create_container(Pid, Image,
+        #{
+            'Tty' => true,
+            'Cmd' => binary:split(Args, <<" ">>, [trim_all, global]),
+            'PublishAllPorts' => true
+        }),
+    lager:notice("Docker container ~s running ~s created: ~s", [Name, Image, Id]),
+    ok = dockerl:start_container(Pid, Id),
+    lager:notice("Docker container ~s started", [Id]),
+    {ok, State#{{?MODULE, containers} => [{Name, Id} | Containers]}};
+
+given([Name, <<"container">>, <<"logs">>, <<"match">>, Pattern], State = #{{?MODULE, pid} := Pid, {?MODULE, containers} := Containers}) ->
+    Id = proplists:get_value(Name, Containers),
+    {ok, Stream} = dockerl:container_logs(Pid, Id),
+    ok = match_logs(Stream, Pattern),
+    {ok, State}.
+
+%%===================================================================
+%% Utility functions
+%%===================================================================
+get_port(Name, Port, #{{?MODULE, pid} := Pid, {?MODULE, containers} := Containers}) ->
+    Id = proplists:get_value(Name, Containers),
+    {ok, Ports} = dockerl_utils:get_ports(Pid, Id),
+    [{_, IntPort}] = maps:get(Port, Ports),
+    BinPort = integer_to_binary(IntPort),
+    BinAddress = case os:type() of
+                     {unix, darwin} -> %% Todo: remove this hack once routing is fixed in Docker for Mac
+                         <<"127.0.0.1">>;
+                     _ ->
+                         {ok, Gateway} = dockerl_utils:get_gateway(Pid, Id),
+                         list_to_binary(inet:ntoa(Gateway))
+                 end,
+    {BinAddress, BinPort}.
+
+%%===================================================================
+%% Internal functions
+%%===================================================================
+match_logs(Stream, Pattern) ->
+    receive
+        done ->
+            {error, no_match};
+        Msg ->
+            lager:debug("~p", [Msg]),
+            case re:run(Msg, Pattern) of
+                {match, _} ->
+                    Stream ! stop,
+                    ok;
+                _ ->
+                    match_logs(Stream, Pattern)
+            end
+    after
+        10000 ->
+            Stream ! stop,
+            {error, timeout}
+    end.