diff --git a/README.markdown b/README.markdown
--- a/README.markdown
+++ b/README.markdown
@@ -24,9 +24,10 @@
 
 ## Install
 
-Get Rattletrap by downloading and unpacking [the latest release][] for your
-platform.
+Get Rattletrap by downloading [the latest release][] for your platform.
 
+Rattletrap is also available as [a Docker image][].
+
 To build Rattletrap from source, install [Stack][]. Then run
 `stack --resolver nightly install rattletrap`.
 
@@ -51,7 +52,7 @@
 
 ``` sh
 > rattletrap --help
-rattletrap.EXE version 7.0.0
+rattletrap.EXE version 8.0.0
   -c           --compact         minify JSON output
   -f           --fast            only encode or decode the header
   -h           --help            show the help
@@ -137,4 +138,5 @@
 [C# parser]: https://github.com/jjbott/RocketLeagueReplayParser
 [Rust parser]: https://github.com/nickbabcock/rrrocket
 [the latest release]: https://github.com/tfausak/rattletrap/releases/latest
+[a Docker image]: https://hub.docker.com/r/taylorfausak/rattletrap
 [Stack]: https://docs.haskellstack.org/en/stable/README/
diff --git a/library/Rattletrap/Decode/Common.hs b/library/Rattletrap/Decode/Common.hs
--- a/library/Rattletrap/Decode/Common.hs
+++ b/library/Rattletrap/Decode/Common.hs
@@ -2,6 +2,7 @@
   ( Decode
   , DecodeBits
   , decodeWhen
+  , getBitsLE
   , getByteStringBits
   , getWord8Bits
   , runDecode
@@ -16,15 +17,15 @@
   , Binary.getWord32le
   , Binary.getWord64le
   , BinaryBits.getBool
-  , BinaryBits.getWord16be
-  , BinaryBits.getWord32be
   )
 where
 
 import qualified Control.Applicative as Applicative
+import qualified Control.Monad as Monad
 import qualified Data.Binary as Binary
 import qualified Data.Binary.Bits.Get as BinaryBits
 import qualified Data.Binary.Get as Binary
+import qualified Data.Bits as Bits
 import qualified Data.ByteString as Bytes
 import qualified Data.ByteString.Lazy as LazyBytes
 import qualified Data.Word as Word
@@ -59,3 +60,11 @@
   case runDecode decode (Utility.reverseBytes bytes) of
     Left problem -> fail problem
     Right result -> pure result
+
+getBitsLE :: Bits.Bits a => Int -> BinaryBits.BitGet a
+getBitsLE size = do
+  bits <- Monad.replicateM size BinaryBits.getBool
+  pure $ foldr
+    (\bit x -> let y = Bits.shiftL x 1 in if bit then Bits.setBit y 0 else y)
+    Bits.zeroBits
+    bits
diff --git a/library/Rattletrap/Decode/EnumAttribute.hs b/library/Rattletrap/Decode/EnumAttribute.hs
--- a/library/Rattletrap/Decode/EnumAttribute.hs
+++ b/library/Rattletrap/Decode/EnumAttribute.hs
@@ -7,4 +7,4 @@
 import Rattletrap.Type.EnumAttribute
 
 decodeEnumAttributeBits :: DecodeBits EnumAttribute
-decodeEnumAttributeBits = EnumAttribute <$> getWord16be 11
+decodeEnumAttributeBits = EnumAttribute <$> getBitsLE 11
diff --git a/library/Rattletrap/Decode/PlayerHistoryKeyAttribute.hs b/library/Rattletrap/Decode/PlayerHistoryKeyAttribute.hs
--- a/library/Rattletrap/Decode/PlayerHistoryKeyAttribute.hs
+++ b/library/Rattletrap/Decode/PlayerHistoryKeyAttribute.hs
@@ -8,4 +8,4 @@
 
 decodePlayerHistoryKeyAttributeBits :: DecodeBits PlayerHistoryKeyAttribute
 decodePlayerHistoryKeyAttributeBits =
-  PlayerHistoryKeyAttribute <$> getWord16be 14
+  PlayerHistoryKeyAttribute <$> getBitsLE 14
diff --git a/library/Rattletrap/Decode/ProductAttribute.hs b/library/Rattletrap/Decode/ProductAttribute.hs
--- a/library/Rattletrap/Decode/ProductAttribute.hs
+++ b/library/Rattletrap/Decode/ProductAttribute.hs
@@ -49,16 +49,16 @@
   pure (ProductAttribute flag objectId maybeObjectName value)
 
 decodeSpecialEdition :: DecodeBits ProductAttributeValue
-decodeSpecialEdition = ProductAttributeValueSpecialEdition <$> getWord32be 31
+decodeSpecialEdition = ProductAttributeValueSpecialEdition <$> getBitsLE 31
 
 decodePainted :: (Int, Int, Int) -> DecodeBits ProductAttributeValue
 decodePainted version = if version >= (868, 18, 0)
-  then ProductAttributeValuePaintedNew <$> getWord32be 31
+  then ProductAttributeValuePaintedNew <$> getBitsLE 31
   else ProductAttributeValuePaintedOld <$> decodeCompressedWordBits 13
 
 decodeTeamEdition :: (Int, Int, Int) -> DecodeBits ProductAttributeValue
 decodeTeamEdition version = if version >= (868, 18, 0)
-  then ProductAttributeValueTeamEditionNew <$> getWord32be 31
+  then ProductAttributeValueTeamEditionNew <$> getBitsLE 31
   else ProductAttributeValueTeamEditionOld <$> decodeCompressedWordBits 13
 
 decodeColor :: (Int, Int, Int) -> DecodeBits ProductAttributeValue
@@ -66,7 +66,7 @@
   then ProductAttributeValueUserColorNew <$> decodeWord32leBits
   else do
     hasValue <- getBool
-    ProductAttributeValueUserColorOld <$> decodeWhen hasValue (getWord32be 31)
+    ProductAttributeValueUserColorOld <$> decodeWhen hasValue (getBitsLE 31)
 
 decodeTitle :: DecodeBits ProductAttributeValue
 decodeTitle = ProductAttributeValueTitleId <$> decodeStrBits
diff --git a/library/Rattletrap/Decode/RemoteId.hs b/library/Rattletrap/Decode/RemoteId.hs
--- a/library/Rattletrap/Decode/RemoteId.hs
+++ b/library/Rattletrap/Decode/RemoteId.hs
@@ -18,7 +18,7 @@
 
 decodeRemoteIdBits :: (Int, Int, Int) -> Word8le -> DecodeBits RemoteId
 decodeRemoteIdBits version systemId = case word8leValue systemId of
-  0 -> RemoteIdSplitscreen <$> getWord32be 24
+  0 -> RemoteIdSplitscreen <$> getBitsLE 24
   1 -> RemoteIdSteam <$> decodeWord64leBits
   2 -> RemoteIdPlayStation <$> decodePsName <*> decodePsBytes version
   4 -> RemoteIdXbox <$> decodeWord64leBits
diff --git a/library/Rattletrap/Encode/Attribute.hs b/library/Rattletrap/Encode/Attribute.hs
--- a/library/Rattletrap/Encode/Attribute.hs
+++ b/library/Rattletrap/Encode/Attribute.hs
@@ -10,9 +10,14 @@
 import qualified Data.Binary.Bits.Put as BinaryBits
 
 putAttributes :: [Attribute] -> BinaryBits.BitPut ()
-putAttributes attributes = do
-  mapM_ putAttribute attributes
-  BinaryBits.putBool False
+putAttributes attributes = case attributes of
+  [] -> BinaryBits.putBool False
+  [attribute] -> do
+    putAttribute attribute
+    BinaryBits.putBool False
+  first : rest -> do
+    putAttribute first
+    putAttributes rest
 
 putAttribute :: Attribute -> BinaryBits.BitPut ()
 putAttribute attribute = do
diff --git a/library/Rattletrap/Encode/Common.hs b/library/Rattletrap/Encode/Common.hs
new file mode 100644
--- /dev/null
+++ b/library/Rattletrap/Encode/Common.hs
@@ -0,0 +1,10 @@
+module Rattletrap.Encode.Common
+  ( putBitsLE
+  )
+where
+
+import qualified Data.Binary.Bits.Put as BinaryBits
+import qualified Data.Bits as Bits
+
+putBitsLE :: Bits.Bits a => Int -> a -> BinaryBits.BitPut ()
+putBitsLE size x = mapM_ (BinaryBits.putBool . Bits.testBit x) [0 .. size - 1]
diff --git a/library/Rattletrap/Encode/EnumAttribute.hs b/library/Rattletrap/Encode/EnumAttribute.hs
--- a/library/Rattletrap/Encode/EnumAttribute.hs
+++ b/library/Rattletrap/Encode/EnumAttribute.hs
@@ -3,10 +3,11 @@
   )
 where
 
+import Rattletrap.Encode.Common
 import Rattletrap.Type.EnumAttribute
 
 import qualified Data.Binary.Bits.Put as BinaryBits
 
 putEnumAttribute :: EnumAttribute -> BinaryBits.BitPut ()
 putEnumAttribute enumAttribute =
-  BinaryBits.putWord16be 11 (enumAttributeValue enumAttribute)
+  putBitsLE 11 (enumAttributeValue enumAttribute)
diff --git a/library/Rattletrap/Encode/Frame.hs b/library/Rattletrap/Encode/Frame.hs
--- a/library/Rattletrap/Encode/Frame.hs
+++ b/library/Rattletrap/Encode/Frame.hs
@@ -10,7 +10,12 @@
 import qualified Data.Binary.Bits.Put as BinaryBits
 
 putFrames :: [Frame] -> BinaryBits.BitPut ()
-putFrames = mapM_ putFrame
+putFrames frames = case frames of
+  [] -> pure ()
+  [frame] -> putFrame frame
+  first : rest -> do
+    putFrame first
+    putFrames rest
 
 putFrame :: Frame -> BinaryBits.BitPut ()
 putFrame frame = do
diff --git a/library/Rattletrap/Encode/PlayerHistoryKeyAttribute.hs b/library/Rattletrap/Encode/PlayerHistoryKeyAttribute.hs
--- a/library/Rattletrap/Encode/PlayerHistoryKeyAttribute.hs
+++ b/library/Rattletrap/Encode/PlayerHistoryKeyAttribute.hs
@@ -3,11 +3,11 @@
   )
 where
 
+import Rattletrap.Encode.Common
 import Rattletrap.Type.PlayerHistoryKeyAttribute
 
 import qualified Data.Binary.Bits.Put as BinaryBits
 
 putPlayerHistoryKeyAttribute
   :: PlayerHistoryKeyAttribute -> BinaryBits.BitPut ()
-putPlayerHistoryKeyAttribute =
-  BinaryBits.putWord16be 14 . playerHistoryKeyAttributeUnknown
+putPlayerHistoryKeyAttribute = putBitsLE 14 . playerHistoryKeyAttributeUnknown
diff --git a/library/Rattletrap/Encode/ProductAttribute.hs b/library/Rattletrap/Encode/ProductAttribute.hs
--- a/library/Rattletrap/Encode/ProductAttribute.hs
+++ b/library/Rattletrap/Encode/ProductAttribute.hs
@@ -3,6 +3,7 @@
   )
 where
 
+import Rattletrap.Encode.Common
 import Rattletrap.Encode.CompressedWord
 import Rattletrap.Encode.Word32le
 import Rattletrap.Encode.Word8le
@@ -23,14 +24,14 @@
   putWord32Bits (productAttributeObjectId attribute)
   case productAttributeValue attribute of
     ProductAttributeValuePaintedOld x -> putCompressedWord x
-    ProductAttributeValuePaintedNew x -> BinaryBits.putWord32be 31 x
+    ProductAttributeValuePaintedNew x -> putBitsLE 31 x
     ProductAttributeValueTeamEditionOld x -> putCompressedWord x
-    ProductAttributeValueTeamEditionNew x -> BinaryBits.putWord32be 31 x
-    ProductAttributeValueSpecialEdition x -> BinaryBits.putWord32be 31 x
+    ProductAttributeValueTeamEditionNew x -> putBitsLE 31 x
+    ProductAttributeValueSpecialEdition x -> putBitsLE 31 x
     ProductAttributeValueUserColorOld x -> case x of
       Nothing -> BinaryBits.putBool False
       Just y -> do
         BinaryBits.putBool True
-        BinaryBits.putWord32be 31 y
+        putBitsLE 31 y
     ProductAttributeValueUserColorNew x -> putWord32Bits x
     ProductAttributeValueTitleId x -> putTextBits x
diff --git a/library/Rattletrap/Encode/RemoteId.hs b/library/Rattletrap/Encode/RemoteId.hs
--- a/library/Rattletrap/Encode/RemoteId.hs
+++ b/library/Rattletrap/Encode/RemoteId.hs
@@ -3,6 +3,7 @@
   )
 where
 
+import Rattletrap.Encode.Common
 import Rattletrap.Encode.Word64le
 import Rattletrap.Type.RemoteId
 import Rattletrap.Type.Word64le
@@ -20,7 +21,7 @@
   RemoteIdPsyNet e -> case e of
     Left l -> putWord64Bits l
     Right (a, b, c, d) -> putWord256 a b c d
-  RemoteIdSplitscreen word24 -> BinaryBits.putWord32be 24 word24
+  RemoteIdSplitscreen word24 -> putBitsLE 24 word24
   RemoteIdSteam word64 -> putWord64Bits word64
   RemoteIdSwitch a b c d -> putWord256 a b c d
   RemoteIdXbox word64 -> putWord64Bits word64
diff --git a/library/Rattletrap/Encode/Replication.hs b/library/Rattletrap/Encode/Replication.hs
--- a/library/Rattletrap/Encode/Replication.hs
+++ b/library/Rattletrap/Encode/Replication.hs
@@ -10,9 +10,14 @@
 import qualified Data.Binary.Bits.Put as BinaryBits
 
 putReplications :: [Replication] -> BinaryBits.BitPut ()
-putReplications replications = do
-  mapM_ putReplication replications
-  BinaryBits.putBool False
+putReplications replications = case replications of
+  [] -> BinaryBits.putBool False
+  [replication] -> do
+    putReplication replication
+    BinaryBits.putBool False
+  first : rest -> do
+    putReplication first
+    putReplications rest
 
 putReplication :: Replication -> BinaryBits.BitPut ()
 putReplication replication = do
diff --git a/rattletrap.cabal b/rattletrap.cabal
--- a/rattletrap.cabal
+++ b/rattletrap.cabal
@@ -1,7 +1,7 @@
 cabal-version: 2.2
 
 name: rattletrap
-version: 8.0.0
+version: 9.0.0
 
 category: Game
 description: Rattletrap parses and generates Rocket League replays.
@@ -140,6 +140,7 @@
     Rattletrap.Encode.CamSettingsAttribute
     Rattletrap.Encode.ClassMapping
     Rattletrap.Encode.ClubColorsAttribute
+    Rattletrap.Encode.Common
     Rattletrap.Encode.CompressedWord
     Rattletrap.Encode.CompressedWordVector
     Rattletrap.Encode.Content
@@ -301,6 +302,7 @@
     HUnit ^>= 1.6.0,
     rattletrap,
     temporary ^>= 1.3,
+  ghc-options: -rtsopts -threaded
   hs-source-dirs: tests
   main-is: Main.hs
   type: exitcode-stdio-1.0
