diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 9cbda10..8a0ad8d 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -24,12 +24,12 @@ repos:
     hooks:
     -   id: add-trailing-comma
 -   repo: https://github.com/asottile/pyupgrade
-    rev: v3.15.0
+    rev: v3.15.1
     hooks:
     -   id: pyupgrade
         args: [--py39-plus]
 -   repo: https://github.com/hhatto/autopep8
-    rev: v2.0.4
+    rev: v2.1.0
     hooks:
     -   id: autopep8
 -   repo: https://github.com/PyCQA/flake8
@@ -37,7 +37,7 @@ repos:
     hooks:
     -   id: flake8
 -   repo: https://github.com/pre-commit/mirrors-mypy
-    rev: v1.8.0
+    rev: v1.9.0
     hooks:
     -   id: mypy
         additional_dependencies: [types-all]
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6c2ee94..076e163 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,20 @@
+3.7.0 - 2024-03-24
+==================
+
+### Features
+- Use a tty for `docker` and `docker_image` hooks when `--color` is specified.
+    - #3122 PR by @glehmann.
+
+### Fixes
+- Fix `fail_fast` for individual hooks stopping when previous hooks had failed.
+    - #3167 issue by @tp832944.
+    - #3168 PR by @asottile.
+
+### Updating
+- The per-hook behaviour of `fail_fast` was fixed.  If you want the pre-3.7.0
+  behaviour, add `fail_fast: true` to all hooks before the last `fail_fast`
+  hook.
+
 3.6.2 - 2024-02-18
 ==================
 
diff --git a/pre_commit/commands/run.py b/pre_commit/commands/run.py
index 076f16d..2a08dff 100644
--- a/pre_commit/commands/run.py
+++ b/pre_commit/commands/run.py
@@ -298,7 +298,7 @@ def _run_hooks(
             verbose=args.verbose, use_color=args.color,
         )
         retval |= current_retval
-        if retval and (config['fail_fast'] or hook.fail_fast):
+        if current_retval and (config['fail_fast'] or hook.fail_fast):
             break
     if retval and args.show_diff_on_failure and prior_diff:
         if args.all_files:
diff --git a/pre_commit/languages/docker.py b/pre_commit/languages/docker.py
index 2632851..4de1d58 100644
--- a/pre_commit/languages/docker.py
+++ b/pre_commit/languages/docker.py
@@ -108,10 +108,15 @@ def get_docker_user() -> tuple[str, ...]:  # pragma: win32 no cover
         return ()
 
 
-def docker_cmd() -> tuple[str, ...]:  # pragma: win32 no cover
+def get_docker_tty(*, color: bool) -> tuple[str, ...]:  # pragma: win32 no cover  # noqa: E501
+    return (('--tty',) if color else ())
+
+
+def docker_cmd(*, color: bool) -> tuple[str, ...]:  # pragma: win32 no cover
     return (
         'docker', 'run',
         '--rm',
+        *get_docker_tty(color=color),
         *get_docker_user(),
         # https://docs.docker.com/engine/reference/commandline/run/#mount-volumes-from-container-volumes-from
         # The `Z` option tells Docker to label the content with a private
@@ -139,7 +144,7 @@ def run_hook(
 
     entry_tag = ('--entrypoint', entry_exe, docker_tag(prefix))
     return lang_base.run_xargs(
-        (*docker_cmd(), *entry_tag, *cmd_rest),
+        (*docker_cmd(color=color), *entry_tag, *cmd_rest),
         file_args,
         require_serial=require_serial,
         color=color,
diff --git a/pre_commit/languages/docker_image.py b/pre_commit/languages/docker_image.py
index a1a2c16..60caa10 100644
--- a/pre_commit/languages/docker_image.py
+++ b/pre_commit/languages/docker_image.py
@@ -23,7 +23,7 @@ def run_hook(
         require_serial: bool,
         color: bool,
 ) -> tuple[int, bytes]:  # pragma: win32 no cover
-    cmd = docker_cmd() + lang_base.hook_cmd(entry, args)
+    cmd = docker_cmd(color=color) + lang_base.hook_cmd(entry, args)
     return lang_base.run_xargs(
         cmd,
         file_args,
diff --git a/setup.cfg b/setup.cfg
index a447bbb..0e15560 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,6 +1,6 @@
 [metadata]
 name = pre_commit
-version = 3.6.2
+version = 3.7.0
 description = A framework for managing and maintaining multi-language pre-commit hooks.
 long_description = file: README.md
 long_description_content_type = text/markdown
diff --git a/tests/commands/run_test.py b/tests/commands/run_test.py
index e36a3ca..50a20f3 100644
--- a/tests/commands/run_test.py
+++ b/tests/commands/run_test.py
@@ -1088,6 +1088,22 @@ def test_fail_fast_per_hook(cap_out, store, repo_with_failing_hook):
     assert printed.count(b'Failing hook') == 1
 
 
+def test_fail_fast_not_prev_failures(cap_out, store, repo_with_failing_hook):
+    with modify_config() as config:
+        config['repos'].append({
+            'repo': 'meta',
+            'hooks': [
+                {'id': 'identity', 'fail_fast': True},
+                {'id': 'identity', 'name': 'run me!'},
+            ],
+        })
+    stage_a_file()
+
+    ret, printed = _do_run(cap_out, store, repo_with_failing_hook, run_opts())
+    # should still run the last hook since the `fail_fast` one didn't fail
+    assert printed.count(b'run me!') == 1
+
+
 def test_classifier_removes_dne():
     classifier = Classifier(('this_file_does_not_exist',))
     assert classifier.filenames == []
diff --git a/tests/languages/docker_image_test.py b/tests/languages/docker_image_test.py
index 7993c11..4e3a878 100644
--- a/tests/languages/docker_image_test.py
+++ b/tests/languages/docker_image_test.py
@@ -25,3 +25,27 @@ def test_docker_image_hook_via_args(tmp_path):
         args=('hello hello world',),
     )
     assert ret == (0, b'hello hello world\n')
+
+
+@xfailif_windows  # pragma: win32 no cover
+def test_docker_image_color_tty(tmp_path):
+    ret = run_language(
+        tmp_path,
+        docker_image,
+        'ubuntu:22.04',
+        args=('grep', '--color', 'root', '/etc/group'),
+        color=True,
+    )
+    assert ret == (0, b'\x1b[01;31m\x1b[Kroot\x1b[m\x1b[K:x:0:\n')
+
+
+@xfailif_windows  # pragma: win32 no cover
+def test_docker_image_no_color_no_tty(tmp_path):
+    ret = run_language(
+        tmp_path,
+        docker_image,
+        'ubuntu:22.04',
+        args=('grep', '--color', 'root', '/etc/group'),
+        color=False,
+    )
+    assert ret == (0, b'root:x:0:\n')