<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Mateusz Cholewka</title>
    <description>The latest articles on Forem by Mateusz Cholewka (@mtk3d).</description>
    <link>https://forem.com/mtk3d</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F266103%2F31efc647-ecbc-49c9-a37a-3745ae0d8ec3.jpg</url>
      <title>Forem: Mateusz Cholewka</title>
      <link>https://forem.com/mtk3d</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/mtk3d"/>
    <language>en</language>
    <item>
      <title>Beyond the Hype: Why We Chose Redis Streams Over Kafka for Microservices Communication</title>
      <dc:creator>Mateusz Cholewka</dc:creator>
      <pubDate>Tue, 19 Aug 2025 23:10:01 +0000</pubDate>
      <link>https://forem.com/mtk3d/beyond-the-hype-why-we-chose-redis-streams-over-kafka-for-our-microservices-dmc</link>
      <guid>https://forem.com/mtk3d/beyond-the-hype-why-we-chose-redis-streams-over-kafka-for-our-microservices-dmc</guid>
      <description>&lt;p&gt;I'm writing this article based on my personal experience implementing a production-ready solution using Redis Streams with my team. This is a result of a few months working with Redis Streams, development and maintenance. If you're not sure, which solution to choose, I hope this article helps you choose the best solution for your needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;The problem we were facing was quite common. The question was how to communicate between two microservices in a simple, fast, and reliable way. Our first thought was to choose between two of the most popular solutions: RabbitMQ or Kafka. We wanted something that could handle higher throughput in the future, so it seemed, that Kafka would be better choice. It met all of the above requirements, and it's also a resume-friendly solution.&lt;/p&gt;

&lt;p&gt;However, there were two problems: we didn't have it in our stack yet, and nobody on the team had experience with Kafka or operating a Kafka server. Also, since the project runs on PHP, integration is not as straightforward as it is with Java for example. Additionally, a PHP extension to integrate with Kafka was not well supported these days, and we didn't feel comfortable using it. Nowadays, of course, the situation is different, and there are some stable solutions for PHP Kafka.&lt;/p&gt;

&lt;p&gt;When we were focused on learning Kafka, and all the features that we don't actually need, someone suggested that Redis has a messaging system called Redis Streams. So we began researching it.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Redis can be an alternative to Kafka?
&lt;/h2&gt;

&lt;p&gt;When I first heard about Streams, I thought, "Okay, I'm not sure if it will help, but let's see." I started researching, and step by step, I became more and more convinced that it actually meets all of our expectations, and it's much easier from operational perspective. Also we already had it in our stack.&lt;/p&gt;

&lt;p&gt;So, we had to compare Kafka and Redis. The best option is probably to write down some architectural drivers and compare them. Let's take a few:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Persistence and replayability: We don't need too much of this.&lt;/li&gt;
&lt;li&gt;Operational complexity: It's an MVP, so it needs to be as simple as possible.&lt;/li&gt;
&lt;li&gt;Performance: We might need to handle high throughput in the future.&lt;/li&gt;
&lt;li&gt;Learning curve: Smaller is better.&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Driver&lt;/th&gt;
&lt;th&gt;Kafka&lt;/th&gt;
&lt;th&gt;Redis Streams&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;persistence and replayability&lt;/td&gt;
&lt;td&gt;persisting in cheaper disk space, and can replay from the very beginning&lt;/td&gt;
&lt;td&gt;persisting data in more expensive RAM, long streams are possible but more expensive&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;operational complexity&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Low or Very Low if you have Redis already in stack&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;performance&lt;/td&gt;
&lt;td&gt;Highest throughput&lt;br&gt;Medium latency&lt;/td&gt;
&lt;td&gt;High throughput&lt;br&gt;Low latency&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;learning curve&lt;/td&gt;
&lt;td&gt;Steep, needs to understand how the system works and how to integrate it&lt;/td&gt;
&lt;td&gt;Gentle, the same well known API as used in many different cases. Easy to integrate, every language have it applied&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;Kafka often achieves higher throughput at the expense of higher latency, while Redis Streams has lower latency but its maximum throughput may be lower.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Following those few most important drivers, it seems that Redis Streams could be a good solution. What still leaves me unconvinced is the lack of information about the real problems we could face. Most articles are theoretical. That's also why I decided to write this post.&lt;/p&gt;

&lt;p&gt;Thankfully, I finally found a document that totally convinced me. It was official document, prepared by Redis, and it dives deeper into the details of the comparison between Kafka and Redis Streams. There are many examples with visual presentations. You can download the document from the Redis website: &lt;a href="https://redis.io/resources/understanding-streams-in-redis-and-kafka-a-visual-guide/" rel="noopener noreferrer"&gt;https://redis.io/resources/understanding-streams-in-redis-and-kafka-a-visual-guide/&lt;/a&gt;. This document also shows some great ideas, how to achieve similar features as in Kafka, like partitioning.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Redis Streams works?
&lt;/h2&gt;

&lt;p&gt;The above document explains everything very well, but I will describe the basics here. I encourage you to test these commands using Redis Insight (&lt;a href="https://redis.io/insight/" rel="noopener noreferrer"&gt;https://redis.io/insight/&lt;/a&gt;). This will demonstrate how simple it is to start using streams.&lt;/p&gt;

&lt;h3&gt;
  
  
  Simple messaging
&lt;/h3&gt;

&lt;p&gt;The easiest way to start working with streams is to set up a stream and start consuming messages. In fact, the only way to create a stream is to simply start sending messages to it. If you try:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;XADD mymessages * payload "This is the payload of a message"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will automatically create the stream named &lt;code&gt;mymessages&lt;/code&gt; and add a message with a &lt;code&gt;payload&lt;/code&gt; field containing the message in quotes.&lt;/p&gt;

&lt;p&gt;Okay, so now we should read this message somehow. We can do that by running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;XREAD COUNT 1 STREAMS mymessages 0-0

Output:
1) 1) "mymessages"
   2) 1) 1) 1526984818136-0
         2) 1) "payload"
            2) This is the payload of a message"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Where &lt;code&gt;1&lt;/code&gt; you can specify how many messages you want to read from the stream at once. You can increase this number if you're processing more messages at once. After reading and processing it properly, you can remove the message by running the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;XDEL mymessages 1526984818136-0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will clean up already processed messages from our stream.&lt;/p&gt;

&lt;p&gt;That's how it works for the simplest case, with one consumer.&lt;/p&gt;

&lt;h3&gt;
  
  
  Consumer groups
&lt;/h3&gt;

&lt;p&gt;Okay, we know how to use streams for one consumer. But the real world is not that simple. You may want to read streams from multiple instances of a single microservice or even multiple microservices. How can we manage this case while ensuring deliverability and preventing one message from being processed multiple times? This is where consumer groups shine.&lt;/p&gt;

&lt;p&gt;To create a consumer group, simply run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;XGROUP CREATE mymessages mygroup 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can use any name to describe the group of services that will consume those messages. Next, when you want to assign a specific instance of a microservice, for example, what do you do? As always, there is an upsert approach, so you just need to start listening from a specific group.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;XREADGROUP GROUP mygroup consumerid STREAMS mymessages 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What is &lt;code&gt;consumerid&lt;/code&gt;? It can be any string that helps you identify the specific consumer in case of any problems with consumption. For example, if you're using Kubernetes, you can use the container ID. This will help you identify the correct container in your infrastructure.&lt;/p&gt;

&lt;p&gt;Since Redis promises to guarantee message delivery within a group, we need to inform it when a message has been properly processed. Otherwise, Redis won't know if it can take another entry from the stream, and it won't be able to properly share entries between all consumers. That's why we need to acknowledge it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;XACK mymessages mygroup 1526984818136-0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the message is marked as acknowledged and will not be delivered again to the same consumer group.&lt;/p&gt;

&lt;h3&gt;
  
  
  Control stream length
&lt;/h3&gt;

&lt;p&gt;As with multiple consumer groups you need to keep longer streams. You can't just remove message after processing, because they have to be delivered to all groups. Since messages are stored in memory, you should monitor, and control its size to avoid overflow. You can control the size manually by running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;XTRIM mymessages MAXLEN 1000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will reduce the length of the stream to 1,000 by removing the oldest messages. However, this method may be inefficient.&lt;/p&gt;

&lt;p&gt;There is a better solution. You can configure Redis to automatically adjust the stream length, by modifying XADD command following way:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;XADD mymessages MAXLEN ~ 1000 * payload "This is the payload of a message"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You've probably noticed the &lt;code&gt;~&lt;/code&gt; after &lt;code&gt;MAXLEN&lt;/code&gt;; it stands for &lt;strong&gt;"approximate trimming."&lt;/strong&gt; This makes Redis less strict about the stream's length, allowing it to trim messages asynchronously in the background. This approach is far more efficient as it doesn't block write operations. In my case, with the stream length oscillating around just a few messages, so this shouldn't be a problem in most cases.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real world problems
&lt;/h2&gt;

&lt;p&gt;Of course, the real world is not as beautiful as documentation will describe to you. There are always known problems that you'll only face when you start using Redis. It's also good to mention that every specific use case can cause different problems and expose different limitations. I've encountered some of these problems and will describe them here, along with possible solutions that you can use when you encounter them.&lt;/p&gt;

&lt;h3&gt;
  
  
  What if Redis is out?
&lt;/h3&gt;

&lt;p&gt;The first thing you should consider if you want to use this solution in production is ensuring high availability. There are two main ways to do so: Redis Sentinel or Redis Cluster. I won't show you how to configure either of them in your infrastructure because that would require an entire article, and this one is already quite long. However, there is an alternative if you're building an MVP and have less time for configuration.&lt;/p&gt;

&lt;p&gt;If your project is written in Node.js, there is one more option. There's a library called ioredis that has a very useful feature. It's an &lt;a href="https://github.com/redis/ioredis?tab=readme-ov-file#offline-queue" rel="noopener noreferrer"&gt;offline queue&lt;/a&gt;. What does this mean? When you have a single instance of Redis without any high availability configuration and Redis goes down, either on purpose or accidentally, ioredis will keep all the redis commands you've sent, in memory. Once Redis is back, all the queued commands will be sent to Redis, and all your subsequent commands will be sent normally. In this case, you won't lose any messages, even if Redis stops working.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F42fo1w4c21erteh757ki.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F42fo1w4c21erteh757ki.png" alt="Redis Streams Offline Queue Diagram" width="800" height="645"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I don’t know if any library in other languages has a similar feature, but I can describe what it looks like in PHP. In PHP, there are probably two main Redis libraries: Predis and PHPRedis (as a C extension). Unfortunately, neither of them provides this functionality. If you need this functionality and cannot ensure an HA configuration, you can try implementing it yourself. It's probably possible by using APCu storage, which works between all PHP-FPM workers and is a shared storage. However, a better option is to implement a cluster or a sentinel.&lt;/p&gt;

&lt;h3&gt;
  
  
  Memory size and stream length
&lt;/h3&gt;

&lt;p&gt;Redis stores data in memory, which makes it really fast, that's well-known. However, this can cause problems, especially with streams. Messages can have different-sized payloads, and sometimes you know the size of the message you can get, but sometimes you have no control over that. For example, consider processing data received from an external system via webhooks.&lt;/p&gt;

&lt;p&gt;What is the problem here? Redis Streams actually allows you to set the size of a stream, but only for its length. There is no option to set the maximum memory consumption of a stream. This small detail can sometimes cause problems. How can this problem be handled? There are a few possible solutions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Compress the payload&lt;/strong&gt; - This solution is simple and effective. Compression is widely adopted by many programming languages, and you can choose from many different compression algorithms. The easiest way to implement this is to use gzip, which is probably available everywhere. You can take the metadata and send it in one non-compressed field. You can add data, such as an identifier or information that helps you decide whether the message should be processed and whether it's worth uncompressing. In our case, compressing a JSON payload gives us a compression ratio of around 3. It's also good to mention that you can send compressed binary data directly into the Redis Stream entry field. If you use Redis Insight, you can set the compression method in the connection settings and see the uncompressed data in the stream preview.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fohvwa5eyczvnjmdxdjse.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fohvwa5eyczvnjmdxdjse.png" alt="Redis Insight Compression Settings" width="800" height="483"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hybrid Solution&lt;/strong&gt; - If you know you'll have to process huge message payloads, you should consider a hybrid solution. For example, you could send only the metadata of a message using a stream and store the rest in S3. This solution is complex, but it will save you if you encounter such a situation. Metadata is more predictable, that can be passed through a stream. By setting the proper stream length, you can easily control memory usage.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foczebq9rzxfjp6vw786p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foczebq9rzxfjp6vw786p.png" alt="Redis Hybrid Solution Architecture" width="800" height="333"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use XACKDEL or XDEL&lt;/strong&gt; - if you don't need to store messages for re-reading after processing; you can just delete them. You can use &lt;code&gt;XDEL&lt;/code&gt;, but be careful — &lt;code&gt;XDEL&lt;/code&gt; removes an entry regardless of whether it was acknowledged by all consumer groups. Since Redis 8.2, you can use &lt;code&gt;XACKDEL&lt;/code&gt;, which is useful if you have multiple consumer groups and want to maintain delivery to all groups. You can choose a strategy for removing the entry. For example, you can use ACKED, which removes the entry after processing only if all consumers have acknowledged the message. This option was added to Redis 8.2, which was released a week ago, so it was not available when I started using Redis Streams.&lt;/p&gt;

&lt;h3&gt;
  
  
  Data eviction policy and streams
&lt;/h3&gt;

&lt;p&gt;This point relates to the previous one. Redis has several strategies for when it runs out of memory. You can remove the latest keys, random keys, etc. Intuitively, you might think that setting maxmemory to remove the oldest keys would remove the oldest messages and everything would be fine. However, that's not the case: &lt;a href="https://github.com/redis/redis/issues/4543" rel="noopener noreferrer"&gt;https://github.com/redis/redis/issues/4543&lt;/a&gt;. The problem is that "key" is a keyword. In Redis streams, a stream references a key in terms of Redis. So, if you set this policy to LRU, LFU, or Random, and stream is like any other key, it can be removed entirely.&lt;/p&gt;

&lt;h3&gt;
  
  
  Replaying long streams and persistence
&lt;/h3&gt;

&lt;p&gt;Redis Streams allows you to replay entire stream from the very beginning. Of course only as far as long is your stream, and if you're not using &lt;code&gt;XDEL&lt;/code&gt; after &lt;code&gt;ACK&lt;/code&gt; or &lt;code&gt;XACKDEL&lt;/code&gt;. But if you really need this possibility to exist, you also need to remember that all this data is stored in the memory, so it's not persisted. Even if you don't need to keep very long streams, you should care about the persistence in terms of any outage.&lt;/p&gt;

&lt;p&gt;Redis Streams are working same way as all other keys in Redis, and they are also included into persistence settings. So if you already have Redis in your infrastructure or you're creating a new one, you should take care about this configuration. If you're not familiar with redis persistence, &lt;a href="https://redis.io/docs/latest/operate/oss_and_stack/management/persistence/" rel="noopener noreferrer"&gt;here you can find all the possible options&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Redis Streams is a great alternative to a messaging service like Kafka, especially if you're looking for simplicity and ease of use. After using Redis Streams in production for a year, I can confidently say that it was the right choice for our use case.&lt;/p&gt;

&lt;p&gt;Choose Redis Streams if you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Already have Redis in your stack&lt;/li&gt;
&lt;li&gt;Need low latency and moderate throughput&lt;/li&gt;
&lt;li&gt;Want minimal operational complexity&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Stick with Kafka if you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Need to process millions of messages per second (for sure)&lt;/li&gt;
&lt;li&gt;Need long-term persistence and replay capabilities&lt;/li&gt;
&lt;li&gt;Have a dedicated infrastructure team with knowledge about Kafka&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The YAGNI principle applies perfectly here. If you've started working with a communication system that uses messaging, you probably don't need all the fancy features that other solutions has. For many real-world scenarios, Redis Streams might be the sweet spot between functionality and complexity. Redis Streams are also inexpensive to implement, so if you decide to switch to Kafka in the future, you won't have wasted too much work.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://redis.io/resources/understanding-streams-in-redis-and-kafka-a-visual-guide/" rel="noopener noreferrer"&gt;Understanding Streams in Redis and Kafka – A Visual Guide&lt;/a&gt; - if you're considering to use streams IMO it's obligatory doc to read, even if you're not choosing between Kafka and Redis, the mechanisms of streams are very well visually explained.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://medium.com/@ThreadSafeDiaries/i-benchmarked-kafka-rabbitmq-and-redis-streams-the-winner-surprised-me-cf3f484eb7b2" rel="noopener noreferrer"&gt;Kafka, RabbitMQ, and Redis Streams Benchmark&lt;/a&gt; - this article may help you to choose better solution from performance perspective&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/redis/redis/releases/tag/8.2.0" rel="noopener noreferrer"&gt;Redis Streams 8.2 Release changelog&lt;/a&gt; - since 8.2, you can use &lt;code&gt;XACKDEL&lt;/code&gt; command to safely remove processed messages, and still delivery them to all consumer groups.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/redis/ioredis?tab=readme-ov-file#offline-queue" rel="noopener noreferrer"&gt;Note about offline queue in ioredis&lt;/a&gt; - useful when you have single non HA Redis instance and want to ensure deliverability&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://redis.io/docs/latest/operate/oss_and_stack/management/persistence/" rel="noopener noreferrer"&gt;Redis persistence configuration&lt;/a&gt; - you should take care about that if you want your stream to be resistant to any Redis restart&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;Originaly posted on&lt;/strong&gt; &lt;a href="https://mateuszcholewka.com/post/why-we-chose-redis-streams-over-kafka/" rel="noopener noreferrer"&gt;https://mateuszcholewka.com/post/why-we-chose-redis-streams-over-kafka/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>eventdriven</category>
      <category>backend</category>
      <category>redis</category>
      <category>microservices</category>
    </item>
    <item>
      <title>A messy local environment can slow you down more than you think. In this post, I walk through 5 frequent setup issues and simple ways to clean them up</title>
      <dc:creator>Mateusz Cholewka</dc:creator>
      <pubDate>Sun, 23 Mar 2025 19:42:45 +0000</pubDate>
      <link>https://forem.com/mtk3d/a-messy-local-environment-can-slow-you-down-more-than-you-think-in-this-post-i-walk-through-5-3gf8</link>
      <guid>https://forem.com/mtk3d/a-messy-local-environment-can-slow-you-down-more-than-you-think-in-this-post-i-walk-through-5-3gf8</guid>
      <description>&lt;div class="ltag__link"&gt;
  &lt;a href="/mtk3d" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F266103%2F31efc647-ecbc-49c9-a37a-3745ae0d8ec3.jpg" alt="mtk3d"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/mtk3d/5-local-environment-mistakes-i-see-everywhere-and-how-to-fix-them-properly-3cii" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;5 Local Environment Mistakes I See Everywhere, and How to Fix Them Properly&lt;/h2&gt;
      &lt;h3&gt;Mateusz Cholewka ・ Mar 21&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#devops&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#programming&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#beginners&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#backend&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


</description>
      <category>devops</category>
      <category>programming</category>
      <category>beginners</category>
      <category>backend</category>
    </item>
    <item>
      <title>5 Local Environment Mistakes I See Everywhere, and How to Fix Them Properly</title>
      <dc:creator>Mateusz Cholewka</dc:creator>
      <pubDate>Fri, 21 Mar 2025 20:18:46 +0000</pubDate>
      <link>https://forem.com/mtk3d/5-local-environment-mistakes-i-see-everywhere-and-how-to-fix-them-properly-3cii</link>
      <guid>https://forem.com/mtk3d/5-local-environment-mistakes-i-see-everywhere-and-how-to-fix-them-properly-3cii</guid>
      <description>&lt;p&gt;Every new project brings fresh challenges, but the very first one is always the same: getting it to run locally.&lt;/p&gt;

&lt;p&gt;Every time I start working on a new project, I end up asking other developers how to launch it. Documentation is usually long, outdated, and full of manual steps, like adjusting configs, copying envs, secrets, and so on.&lt;/p&gt;

&lt;p&gt;Luckily, it doesn’t have to be this way. A few small practices can make your local environment clean, repeatable, and easy to work with, not just for you, but for the entire team.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Too Many Steps Instead of One Clean Command
&lt;/h2&gt;

&lt;p&gt;Most common thing I see in readme.md in projects I’m starting working on, is a list of steps to make your local env up and running. And it’s always the same story:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Alright, let’s go:&lt;br&gt;
Install Docker — check.&lt;br&gt;
Install PHP — check.&lt;br&gt;
Install Composer — check...&lt;br&gt;
…&lt;br&gt;
Okay, looks good. Time to run &lt;code&gt;docker compose up&lt;/code&gt; — and... oh, yea it’s failing.&lt;br&gt;
Now I’m messaging another developer: “Hey, I did everything in the README and it’s not working.”&lt;br&gt;
We debug it together.&lt;br&gt;
“Oh, you just have to copy &lt;code&gt;veryimportantfile.txt&lt;/code&gt; to &lt;code&gt;very_veryimportantfile.txt&lt;/code&gt;, then it’ll work.”&lt;br&gt;
Cool. Let’s add the 2137th step to the setup instructions, then next developer won’t make same mistake…&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In my opinion, every project should be bootstrapped with a single command. One. Not seven, not five, just one. It doesn’t matter if you use Docker, install local dependencies manually, or mix both. Starting the environment should be easy and predictable.&lt;/p&gt;

&lt;p&gt;So instead of this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;    ## Requirements
&lt;span class="p"&gt;    -&lt;/span&gt; docker
&lt;span class="p"&gt;    -&lt;/span&gt; php &amp;gt;= 8.0
    ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can simply make this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;    &lt;span class="c"&gt;# ./launch.sh:&lt;/span&gt;
    &lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nb"&gt;command&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; brew &amp;amp;&amp;gt; /dev/null&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
      &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"❌ Homebrew is not installed. Install it from https://brew.sh/"&lt;/span&gt;
      &lt;span class="nb"&gt;exit &lt;/span&gt;1
    &lt;span class="k"&gt;fi

    if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nb"&gt;command&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; docker &amp;amp;&amp;gt; /dev/null&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
      &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"⚙️ Installing Docker..."&lt;/span&gt;
      brew &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--cask&lt;/span&gt; docker
    &lt;span class="k"&gt;fi

    if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nb"&gt;command&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; php &amp;amp;&amp;gt; /dev/null&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
      &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"🐘 Installing PHP..."&lt;/span&gt;
      brew &lt;span class="nb"&gt;install &lt;/span&gt;php@8.3
    &lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Isn’t that simpler? You don’t need to update the docs, just update script here and that’s it.&lt;/p&gt;

&lt;p&gt;Next, you have to bootstrap your project. Do you need to copy &lt;code&gt;.env.dist&lt;/code&gt; ? Let’s copy it here:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;    &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; .env &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;cp&lt;/span&gt; .env.local .env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You need to run yarn? That’s your next bash script line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;    yarn
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You need to run docker compo…, and so on, until your project will be up and running.&lt;/p&gt;

&lt;p&gt;And one more thing — make your bootstrap script idempotent.&lt;br&gt;
That means every operation should give the same result no matter how many times it runs. Installing dependencies? Only if they’re not already installed. Copying files? Only if they don’t exist.&lt;br&gt;
It makes your script faster, safer, and a lot less likely to break because "something was already there".&lt;/p&gt;

&lt;p&gt;That rule, allows you to keep your readme very simple and clean, with only the most important things.&lt;/p&gt;
&lt;h2&gt;
  
  
  2. Make CLI, but don’t reinvent the wheel, use existing tools
&lt;/h2&gt;

&lt;p&gt;All applications are different, and all of them require different actions to do, during development, like make migrations, run tests, reset database etc. Different frameworks already have some CLI commands for such actions, but there are also many of others. To make it easy for developers, you can build CLI specifically for your project, which collect all useful actions in one place. But how to create that?&lt;/p&gt;

&lt;p&gt;There are plenty of solid, battle-tested tools out there like &lt;code&gt;Makefile&lt;/code&gt;, &lt;code&gt;Taskfile&lt;/code&gt;, or even plain shell scripts. They work cross-platform, they’re easy to read, and they don’t require extra effort to set up or maintain. In most cases, they’re more than enough.&lt;/p&gt;

&lt;p&gt;Avoid building custom CLIs that need to be compiled or rely on specific runtimes. I met with the projects that has some tools like that, compiled for linux, and it was hard to run them on mac or windows. I had to start a virtual machine with linux to run them. I can’t imagine what will I do if they will stop working some day, and I don’t have any source code of them, because it was not shared when transfering application between software houses.&lt;/p&gt;
&lt;h2&gt;
  
  
  3. Make developers experience friendly
&lt;/h2&gt;

&lt;p&gt;If you want to let developers choose which services to run or ask them for input during setup, keep it simple. Don’t reinvent CLI menus in raw bash. It’s messy, unmaintainable, and hard to debug.&lt;/p&gt;

&lt;p&gt;There are tools built exactly for that. One of the best is &lt;a href="https://github.com/charmbracelet/gum" rel="noopener noreferrer"&gt;Gum&lt;/a&gt; - a small utility that helps build terminal user interfaces with checkboxes, file pickers, prompts, and more:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://camo.githubusercontent.com/60dd732281b2b78a5f352a294852bbe5dacba37c4928d726a30ee2f249ff427e/68747470733a2f2f7668732e636861726d2e73682f7668732d37725271334c7345754a5677687772307866364572372e676966" class="article-body-image-wrapper"&gt;&lt;img src="https://camo.githubusercontent.com/60dd732281b2b78a5f352a294852bbe5dacba37c4928d726a30ee2f249ff427e/68747470733a2f2f7668732e636861726d2e73682f7668732d37725271334c7345754a5677687772307866364572372e676966" alt="Source: https://github.com/charmbracelet/gum" width="1200" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It’s lightweight, works well across platforms, and integrates easily with your scripts. You only need to install it at the very beginning of your bash script.&lt;/p&gt;

&lt;p&gt;Use tools like this to keep your setup flexible and clean. Good DX doesn’t have to be complicated.&lt;/p&gt;
&lt;h2&gt;
  
  
  4. Let developers choose what services to run
&lt;/h2&gt;

&lt;p&gt;Not every developer needs every service running all the time. A frontend dev might not care about your backend profiler or a local database admin tool. Forcing everyone to run the full stack wastes resources and slows things down.&lt;/p&gt;

&lt;p&gt;If you are using docker compose, you can use functionality called profiles. Let’s take this example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;    &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3.9"&lt;/span&gt;

    &lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-app&lt;/span&gt;
        &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
        &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8080:80"&lt;/span&gt;

      &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mysql:8&lt;/span&gt;
        &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;MYSQL_ROOT_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;root&lt;/span&gt;
        &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3306:3306"&lt;/span&gt;

      &lt;span class="na"&gt;mailhog&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mailhog/mailhog&lt;/span&gt;
        &lt;span class="na"&gt;profiles&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;optional&lt;/span&gt;
        &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8025:8025"&lt;/span&gt;

      &lt;span class="na"&gt;adminer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;adminer&lt;/span&gt;
        &lt;span class="na"&gt;profiles&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;optional&lt;/span&gt;
        &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8081:8080"&lt;/span&gt;

      &lt;span class="na"&gt;grafana&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;grafana/grafana-oss:latest&lt;/span&gt;
        &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;grafana&lt;/span&gt;
        &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3000:3000"&lt;/span&gt;
        &lt;span class="na"&gt;profiles&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;monitoring&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can add a &lt;code&gt;COMPOSE_PROFILES=&lt;/code&gt; variable to your project’s &lt;code&gt;.env&lt;/code&gt; file. Docker Compose will read this value automatically and run only the services that match the specified profiles.&lt;br&gt;
For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    COMPOSE_PROFILES=optional,monitoring
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This way, developers can easily control what parts of the stack get started&lt;/p&gt;

&lt;p&gt;You can even take it one step further and write a small bash script using &lt;code&gt;gum&lt;/code&gt; to give developers a clean, interactive UI to select which profiles they want to launch. It makes setup feel more like an app than a checklist.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Don’t make developers import the database data manually
&lt;/h2&gt;

&lt;p&gt;If your project doesn’t use any stack specific tool to setup database, and relies on a database dump to set things up, don’t expect developers to import it by hand. That should be part of the automated bootstrap process, just like anything else.&lt;/p&gt;

&lt;p&gt;You can easily set this up in Docker. For example, if you're using MySQL or MariaDB, just mount a &lt;code&gt;.sql&lt;/code&gt; dump into the container. MySQL will automatically import it on first run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;    &lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mysql:8&lt;/span&gt;
        &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;MYSQL_DATABASE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app&lt;/span&gt;
          &lt;span class="na"&gt;MYSQL_ROOT_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;root&lt;/span&gt;
        &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./dump.sql:/docker-entrypoint-initdb.d/dump.sql&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That’s it. First time the container starts, it’ll load the dump. No need to run &lt;code&gt;mysql -u root&lt;/code&gt; by hand. No need to write it in the README. And no chance for someone to skip the step or break things.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bonus: MacOS and Docker Desktop limitations
&lt;/h2&gt;

&lt;p&gt;On macOS, Docker runs inside a VM, most developers use Docker Desktop, which works but comes with two main issues:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Licensing&lt;/strong&gt; - it's free only for small teams. Larger companies need a paid plan (check Docker's terms).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Manual configuration&lt;/strong&gt; - Docker Desktop’s default memory settings are too low for many real-world projects, and increasing them requires opening the UI. You can’t configure it from the terminal, which makes automation impossible and leads to confusing, silent errors.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A better option is Colima. It replaces Docker Desktop, uses the exact same CLI (&lt;code&gt;docker&lt;/code&gt;), and gives you full control via the terminal, so you can set it up in your launch script.&lt;/p&gt;

&lt;p&gt;To start Colima with more RAM, just run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;    colima start &lt;span class="nt"&gt;--memory&lt;/span&gt; 16
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you're running Docker with 16GB of RAM, no licensing issues, and full automation support. One command and you're ready to go, clean and repeatable.&lt;/p&gt;

&lt;p&gt;Linux users don’t have this problem, as Docker runs natively there.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Local environments don’t have to be painful. With a few simple rules you can make setup smooth for everyone on the team.&lt;br&gt;
Use existing tools, avoid overengineering, and document only what you can't automate. It’ll save hours on onboarding, reduce frustration, and make your project feel a lot more professional.&lt;/p&gt;

&lt;p&gt;You can find more articles like this at mateuszcholewka.com. Got a question? Drop a comment below or reach out on LinkedIn.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>programming</category>
      <category>beginners</category>
      <category>backend</category>
    </item>
    <item>
      <title>Introducing LaraTUI: A Terminal UI for Laravel Environments</title>
      <dc:creator>Mateusz Cholewka</dc:creator>
      <pubDate>Mon, 24 Jun 2024 23:02:13 +0000</pubDate>
      <link>https://forem.com/mtk3d/introducing-laratui-a-terminal-ui-for-laravel-environments-2b68</link>
      <guid>https://forem.com/mtk3d/introducing-laratui-a-terminal-ui-for-laravel-environments-2b68</guid>
      <description>&lt;p&gt;I'm excited to introduce LaraTUI, a new open-source project I've been working on. LaraTUI is a terminal user interface designed to help you manage your Laravel local environment using PHP. It's still a work in progress!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxblwfln8bs4qc9wc8q37.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxblwfln8bs4qc9wc8q37.png" alt="LaraTUI screenshot" width="800" height="506"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Current Features:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sail Service Info: See your sail services status&lt;/li&gt;
&lt;li&gt;Composer Versions check: Check for new updates to your Composer packages&lt;/li&gt;
&lt;li&gt;Migrations info: Get information about new migrations pending to run&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Planned Features:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Artisan Commands: Run Artisan commands directly from the interface.&lt;/li&gt;
&lt;li&gt;Migration Management: View and run pending migrations.&lt;/li&gt;
&lt;li&gt;Sail Service Management: Start/Stop and mange your local sail environment.&lt;/li&gt;
&lt;li&gt;Log Viewing: Access and filter your application logs.&lt;/li&gt;
&lt;li&gt;Environment Variables: Verify your .env settings within the TUI.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why PHP for a TUI?
&lt;/h2&gt;

&lt;p&gt;Using PHP for a terminal UI is not very common, but it offers a great way to leverage the existing Laravel ecosystem and my PHP knowledge for a new kind of tool. It's also possible thanks to awesome php-tui library: &lt;a href="https://github.com/php-tui/php-tui" rel="noopener noreferrer"&gt;https://github.com/php-tui/php-tui&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Get Involved
&lt;/h2&gt;

&lt;p&gt;LaraTUI is open source, and I'd love to get feedback and contributions from the community.&lt;br&gt;
Check it out on GitHub: &lt;a href="https://github.com/mtk3d/LaraTUI" rel="noopener noreferrer"&gt;https://github.com/mtk3d/LaraTUI&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Feel free to share your thoughts and suggestions!&lt;/p&gt;

&lt;p&gt;Happy coding! 🚀&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>php</category>
      <category>tui</category>
    </item>
    <item>
      <title>Restore missing Command Bus in new Laravel versions</title>
      <dc:creator>Mateusz Cholewka</dc:creator>
      <pubDate>Tue, 22 Mar 2022 00:21:55 +0000</pubDate>
      <link>https://forem.com/mtk3d/restore-missing-command-bus-in-new-laravel-versions-4n65</link>
      <guid>https://forem.com/mtk3d/restore-missing-command-bus-in-new-laravel-versions-4n65</guid>
      <description>&lt;p&gt;Command Bus is a very powerful pattern, which can help you in decoupling your application modules. You can in an easy way separate small actions, and trigger them using a single dependency called CommandBus.&lt;/p&gt;

&lt;p&gt;The cause why this pattern is able to decouple your modules is that actually, the module that is dispatching the command doesn’t have to care about the actual implementation of the action. No matter if that’s a simple DB query, complicated calculations, or calling another remote service. Using this pattern will help you also if you want to implement one of &lt;a href="https://martinfowler.com/bliki/CommandQuerySeparation.html" rel="noopener noreferrer"&gt;CQS&lt;/a&gt; and &lt;a href="https://martinfowler.com/bliki/CQRS.html" rel="noopener noreferrer"&gt;CQRS&lt;/a&gt; patterns.&lt;/p&gt;

&lt;h2&gt;
  
  
  The CommandBus pattern
&lt;/h2&gt;

&lt;p&gt;This pattern requires three elements:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Command&lt;/strong&gt; - is a small class that name should describe the action that you want to call. I should store all data required to execute actual action.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ReserveResource&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;ResourceId&lt;/span&gt; &lt;span class="nv"&gt;$resourceId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;Period&lt;/span&gt; &lt;span class="nv"&gt;$period&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;getId&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;ResourceId&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;resourceId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;getPeriod&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;Period&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;period&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Handler&lt;/strong&gt; - it's the actual implementation of the action. It should implement &lt;code&gt;handle&lt;/code&gt; or &lt;code&gt;__invoke&lt;/code&gt; method that is handling a Command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ReserveResourceHandler&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;ResourceRepository&lt;/span&gt; &lt;span class="nv"&gt;$resourceRepository&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;ReserveResource&lt;/span&gt; &lt;span class="nv"&gt;$reserveResource&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$resource&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;resourceRepository&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$reserveResource&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getId&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
        &lt;span class="nv"&gt;$resource&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;reserve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$reserveResource&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getPeriod&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;resourceRepository&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$resource&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Bus&lt;/strong&gt; - it’s the last element of this puzzle. This is the element that keeps all definitions of relations between Commands and Handlers, and it’s also able to run right Handler. That’s the part that is generic and you can use ready implementations, so implementing that on your own doesn’t make sense.&lt;/p&gt;

&lt;p&gt;Example usage of CommandBus:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$bus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;CommandBus&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Create new CommandBus instance&lt;/span&gt;
&lt;span class="nv"&gt;$bus&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nc"&gt;ReserveResource&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;ReserveResourceHandler&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt; &lt;span class="c1"&gt;// Map command and handler&lt;/span&gt;

&lt;span class="c1"&gt;// Create command&lt;/span&gt;
&lt;span class="nv"&gt;$reserveResource&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ReserveResource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s1"&gt;'affa2136-5b90-4f7c-933e-73cffc39f1d9'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Period&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'2020-12-06 15:30'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'2020-12-06 16:30'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nv"&gt;$bus&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$reserveResource&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Dispatch command&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Bus implementation
&lt;/h2&gt;

&lt;p&gt;There are some PHP, ready implementations of CommandBus. One of them is the component of the popular Symfony framework, it’s called &lt;a href="https://symfony.com/doc/current/components/messenger.html" rel="noopener noreferrer"&gt;Messenger&lt;/a&gt;. You can also use one of The PHP League packages called &lt;a href="https://tactician.thephpleague.com/" rel="noopener noreferrer"&gt;Tactician&lt;/a&gt;. But if you’re programming in Laravel and you need simple as possible CommandBus implementation, there is an option to use the existed part of the framework.&lt;/p&gt;

&lt;p&gt;Older versions of Laravel had an implementation of &lt;a href="https://laravel.com/docs/5.0/bus#creating-commands" rel="noopener noreferrer"&gt;CommandBus out of the box&lt;/a&gt;. Today the name of this feature was changed, and it’s split to Events and Jobs / Queues, but the event can be queued too…??? I do not fully understand this separation, but I know it may be helpful when you need to implement something quickly.&lt;/p&gt;

&lt;p&gt;Ok, never mind let's take a look at Laravel 5 documentation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;Bus&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PurchasePodcast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Auth&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;user&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nc"&gt;Podcast&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;findOrFail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$podcastId&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ok, this is a usage of Facade called &lt;code&gt;Bus&lt;/code&gt;. Let's try to find out if this class is still available in Laravel code. &lt;a href="https://laravel.com/api/8.x/Illuminate/Support/Facades/Bus.html" rel="noopener noreferrer"&gt;Here it is&lt;/a&gt; still available.&lt;br&gt;
Ok, but I don't like facades, I think that the name of them is badly used, and Laravel facades are like hidden dependencies of your classes. It's always better to inject the dependency from the DI container. So I was investigating the code, and I got to the &lt;code&gt;Illuminate\Bus\Dispatcher&lt;/code&gt; class. And that's the part of Laravel framework that is implemented like Command Bus functionality.&lt;/p&gt;

&lt;p&gt;I’m not recommending using this class directly. This class is not specified in documentation and it could be removed or replaced one day in Laravel (it probably won't). Also, you may want to improve the functionalities of your command bus, so using this one directly, will cause the problem in replacing it in all the places of usage.&lt;/p&gt;

&lt;p&gt;Let's create the Adapter that will be also our safe proxy. But also do not include our Adapter directly. Let's use an interface that will allow us to juggle with implementations.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;CommandBus&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$command&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="nv"&gt;$map&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And next the implementation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Bus\Dispatcher&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;IlluminateCommandBus&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;CommandBus&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;Dispatcher&lt;/span&gt; &lt;span class="nv"&gt;$bus&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$command&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;bus&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$command&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="nv"&gt;$map&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;bus&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$map&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we can declare it in the DI container in the service provider. Remember to register it as a Singleton. It's important to get the same instance of the bus in every place of the application.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;register&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;singleton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CommandBus&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;IlluminateCommandBus&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can define the Commands and Handlers map, and the Command Bus it's ready to use.&lt;/p&gt;

&lt;p&gt;So first register&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;register&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cd"&gt;/** @var CommandBus $bus */&lt;/span&gt;
    &lt;span class="nv"&gt;$bus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CommandBus&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$bus&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
        &lt;span class="nc"&gt;ReserveResource&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;ReserveResourceHandler&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;TurnOnResource&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;TurnOnResourceHandler&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;WithdrawResource&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;WithdrawResourceHandler&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;CreateResource&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;CreateResourceHandler&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and use&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ReservationController&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;CommandBus&lt;/span&gt; &lt;span class="nv"&gt;$bus&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;reserve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;ReserveRequest&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$command&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ReserveResource&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;fromRaw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'from'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'to'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;bus&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$command&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>php</category>
      <category>laravel</category>
      <category>cqrs</category>
    </item>
    <item>
      <title>Alpine.js modal transition in Livewire the missing manual part</title>
      <dc:creator>Mateusz Cholewka</dc:creator>
      <pubDate>Sun, 13 Feb 2022 19:51:45 +0000</pubDate>
      <link>https://forem.com/mtk3d/livewire-transitioning-modal-using-alpinejs-47do</link>
      <guid>https://forem.com/mtk3d/livewire-transitioning-modal-using-alpinejs-47do</guid>
      <description>&lt;p&gt;For the last few days, I'm learning how to use the Laravel Livewire framework. I'm really impressed with how well it works, and how fast we can develop working applications using Livewire along with Tailwind CSS. It's a great option for someone who wants to start his own startup as a one-man army. Only you need to know is PHP or more precisely Laravel and some HTML / CSS.&lt;/p&gt;

&lt;h2&gt;
  
  
  Not everything is possible over the wire
&lt;/h2&gt;

&lt;p&gt;HTML over the wire (which is the main concept behind Livewire) is very powerful, but not everything is possible to do using only on the backend. Some UX features require to use of the frontend API.&lt;/p&gt;

&lt;p&gt;One of the things that are not possible using only Livewire is transition animations of HTML elements. There was functionality &lt;code&gt;wire:transition&lt;/code&gt; before the first release of Livewire, but it has been removed in the final release for some reason.&lt;/p&gt;

&lt;p&gt;When you try to look for a Livewire transition in livewire/livewire GitHub issues, almost all of them contain the information, that there are no attributes like &lt;code&gt;wire:transition&lt;/code&gt; and that Livewire supports Alpine.js transitions. That's fine, but there are no working examples, and some GitHub users were a bit confused about that.&lt;/p&gt;

&lt;h2&gt;
  
  
  The solution
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;tl:dr&lt;/strong&gt; If you want just to copy and paste the working solution, please go ahead 😉&lt;br&gt;
I prepared a working modal component in a gist: &lt;a href="https://gist.github.com/mtk3d/699502a70ee9af1cd412ddcb805e20da" rel="noopener noreferrer"&gt;https://gist.github.com/mtk3d/699502a70ee9af1cd412ddcb805e20da&lt;/a&gt;&lt;/p&gt;



&lt;p&gt;First let's create a basic modal in Livewire, just using blade &lt;code&gt;@if&lt;/code&gt; statement.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Modal&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Component&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$content&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nv"&gt;$show&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;mount&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Modal content'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;view&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'livewire.modal'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the blade template:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;wire:click=&lt;/span&gt;&lt;span class="s"&gt;"$set('show', true)"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        Open
    &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
    @if($show)
    &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;{{ $content }}&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;wire:click=&lt;/span&gt;&lt;span class="s"&gt;"$set('show', false)"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            Close
        &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
     &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    @endif
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Of course instead of using &lt;code&gt;$set()&lt;/code&gt; you can set up PHP open/close methods in the component and call them using &lt;code&gt;wire:click&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;After refreshing the page, it should works like this one:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6257x55ecfcx1ew7ho1g.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6257x55ecfcx1ew7ho1g.gif" alt="Image description" width="600" height="324"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ok, and now, let's modify this component, to work with Alpine.js transitions. You need only to change the template part:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;x-data&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;wire:click=&lt;/span&gt;&lt;span class="s"&gt;"$set('show', true)"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        Open
    &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;x-cloak&lt;/span&gt;
        &lt;span class="na"&gt;x-show=&lt;/span&gt;&lt;span class="s"&gt;"$wire.show"&lt;/span&gt;
        &lt;span class="na"&gt;x-transition.opacity.duration.500ms&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;{{ $content }}&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;wire:click=&lt;/span&gt;&lt;span class="s"&gt;"$set('show', false)"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            Close
        &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First, in the main component element (div in this example) add the &lt;code&gt;x-data&lt;/code&gt; attribute. That will inform Alpine.js that this component contains some attributes to process.&lt;/p&gt;

&lt;p&gt;Next remove &lt;code&gt;@if&lt;/code&gt; statement and add &lt;code&gt;x-show&lt;/code&gt; and &lt;code&gt;x-transition&lt;/code&gt; attributes to the modal element. As a parameter for &lt;code&gt;x-show&lt;/code&gt; you can use the value of &lt;code&gt;$show&lt;/code&gt; property from component, &lt;a href="https://laravel-livewire.com/docs/2.x/alpine-js#interacting-with-livewire-from-alpine" rel="noopener noreferrer"&gt;using JS &lt;code&gt;$wire&lt;/code&gt; proxy object&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Do not forget about the &lt;code&gt;x-cloak&lt;/code&gt; attribute, and the CSS definition for it. &lt;code&gt;x-cloak&lt;/code&gt; is the attribute that prevents hidden elements from blinking before the Alpine.js is loaded. By default, it hides the element by CSS, and then it's removed by Alpine.js on load.&lt;/p&gt;

&lt;p&gt;Add to your CSS file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;x-cloak&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt; &lt;span class="cp"&gt;!important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The rest of the code is the same as before. Now our modal should work like this:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsxuazni9a679ke9g2wi8.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsxuazni9a679ke9g2wi8.gif" alt="Image description" width="600" height="324"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And that's it, we have working Alpine.js transitions in Livewire 😁&lt;/p&gt;

</description>
      <category>php</category>
      <category>laravel</category>
      <category>javascript</category>
      <category>livewire</category>
    </item>
    <item>
      <title>Read this before you start using the multistage builds for your docker images</title>
      <dc:creator>Mateusz Cholewka</dc:creator>
      <pubDate>Mon, 22 Nov 2021 00:20:37 +0000</pubDate>
      <link>https://forem.com/mtk3d/read-this-before-you-start-using-the-multistage-builds-for-your-docker-images-21e7</link>
      <guid>https://forem.com/mtk3d/read-this-before-you-start-using-the-multistage-builds-for-your-docker-images-21e7</guid>
      <description>&lt;p&gt;In my &lt;a href="https://dev.to/mtk3d/here-are-the-dockerfile-tips-you-can-use-to-get-your-builds-faster-and-safer-4o1a"&gt;last post&lt;/a&gt;, I showed how to refactor the Dockerfile in your project. I didn't want to make the previous post too long, so I didn't touch the multistage builds. Here is the post where I will show you how to improve the Dockerfile even more, using multistage builds. Using stages in your builds is not hard to use, but there are some things where the below knowledge may help you 😃&lt;/p&gt;

&lt;p&gt;Let's take the result of the last article:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; php:7.4.25-fpm&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=composer:2.1.11 /usr/bin/composer /usr/bin/composer&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;apt update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    libicu-dev&lt;span class="o"&gt;=&lt;/span&gt;67.1-7 &lt;span class="se"&gt;\
&lt;/span&gt;    libgd-dev&lt;span class="o"&gt;=&lt;/span&gt;2.3.0-2 &lt;span class="se"&gt;\
&lt;/span&gt;    libonig-dev&lt;span class="o"&gt;=&lt;/span&gt;6.9.6-1.1 &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nv"&gt;unzip&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;6.0-26 &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    apt purge &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="nt"&gt;--auto-remove&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;docker-php-ext-install &lt;span class="se"&gt;\
&lt;/span&gt;    exif &lt;span class="se"&gt;\
&lt;/span&gt;    gd &lt;span class="se"&gt;\
&lt;/span&gt;    intl &lt;span class="se"&gt;\
&lt;/span&gt;    mbstring &lt;span class="se"&gt;\
&lt;/span&gt;    mysqli &lt;span class="se"&gt;\
&lt;/span&gt;    opcache &lt;span class="se"&gt;\
&lt;/span&gt;    pdo_mysql &lt;span class="se"&gt;\
&lt;/span&gt;    sockets

&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; COMPOSER_ALLOW_SUPERUSER 1&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; composer.json .&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; composer.lock .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;composer &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--no-dev&lt;/span&gt; &lt;span class="nt"&gt;--no-scripts&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;composer dumpautoload &lt;span class="nt"&gt;--optimize&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Don't keep not necessary dependencies
&lt;/h2&gt;

&lt;p&gt;Every dependency in your image is potentially the source of vulnerabilities, and you should keep them up to date. So, it's good practice to keep only a minimum of really required dependencies.&lt;br&gt;&lt;br&gt;
So let's try to get rid of the composer from our final image. Composer is required to install our backend dependencies, but it's not required in the runtime of our app. Actually, you shouldn't keep it in your final image, because every change, like for example &lt;code&gt;composer update&lt;/code&gt; is removed after the container restart, so it's presence may be even confusing.&lt;/p&gt;

&lt;p&gt;You can get rid of this dependency using multistage builds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;composer:2.1.11&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;build&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; composer.json .&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; composer.lock .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;composer &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--no-dev&lt;/span&gt; &lt;span class="nt"&gt;--no-scripts&lt;/span&gt; &lt;span class="nt"&gt;--ignore-platform-reqs&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;composer dumpautoload &lt;span class="nt"&gt;--optimize&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; php:7.4.25-fpm&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;apt update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    libicu-dev&lt;span class="o"&gt;=&lt;/span&gt;67.1-7 &lt;span class="se"&gt;\
&lt;/span&gt;    libgd-dev&lt;span class="o"&gt;=&lt;/span&gt;2.3.0-2 &lt;span class="se"&gt;\
&lt;/span&gt;    libonig-dev&lt;span class="o"&gt;=&lt;/span&gt;6.9.6-1.1 &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nv"&gt;unzip&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;6.0-26 &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    apt purge &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="nt"&gt;--auto-remove&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;docker-php-ext-install &lt;span class="se"&gt;\
&lt;/span&gt;    exif &lt;span class="se"&gt;\
&lt;/span&gt;    gd &lt;span class="se"&gt;\
&lt;/span&gt;    intl &lt;span class="se"&gt;\
&lt;/span&gt;    mbstring &lt;span class="se"&gt;\
&lt;/span&gt;    mysqli &lt;span class="se"&gt;\
&lt;/span&gt;    opcache &lt;span class="se"&gt;\
&lt;/span&gt;    pdo_mysql &lt;span class="se"&gt;\
&lt;/span&gt;    sockets

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=build /app /app&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So, what happened here? As you can see, I've added a new &lt;code&gt;FROM&lt;/code&gt; instruction before the PHP stage. In this way, we can add multiple stages to our docker image. The &lt;code&gt;AS&lt;/code&gt; word is an alias for our stage, that we can use to refer to it. Referring to the stage is helpful if we need to copy something between stages, or use the previous stage as a base image for another stage.&lt;/p&gt;

&lt;p&gt;In this case, I've added a build stage using a composer image, that will install all my dependencies and generate autoload files of my project. Now, when you start building this image, docker will create the build container first. When the build stage is finished, docker will start building the next stage. For the docker build process, those stages are recognized as different images, so the final image will keep only layers from the latest stage of build.&lt;/p&gt;

&lt;p&gt;Also as you can see, I had added the &lt;code&gt;--ignore-platform-reqs&lt;/code&gt; flag. This flag will allow you to install your dependencies even if you don't have the required PHP extensions installed. Otherwise, compose may stop installing process if some packages require extensions that do not exist in the base composer image.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cache
&lt;/h2&gt;

&lt;p&gt;As I've explained before, for docker build our stages are like different images. That makes a small problem for the building process that had confused me when I've tried the first time to build this image using cache from the previously built image. When I tried to build this image a few times in CI/CD adding &lt;code&gt;--cache-from&lt;/code&gt; flag, the docker didn't use the cache. On my first time trying to get it working with using cache, I've spent a lot of time debugging the pipeline, I've even looked for problems in my runner. The problem wasn't in my runner, and what's more that's behavior is expected.&lt;/p&gt;

&lt;p&gt;Because the final image keeps only the layers that belong to the target stage (default the latest target), the final image doesn't have layers of different stages than the target. That's obvious when you think about it because that helps you to make your images smaller. It may be confusing when you create the CI/CD pipeline when you probably don't have this thinking in your mind.&lt;/p&gt;

&lt;p&gt;So how to resolve this problem? The goal is to keep all stage layers to use later as a cache. So we need to keep them during the build and then push them to our registry, to be able to get them on the next builds.&lt;br&gt;&lt;br&gt;
To achieve that, we can use the &lt;code&gt;--target&lt;/code&gt; flag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;docker pull myimage:latest-build | true
docker pull myimage:latest | true

docker build . --target=build --cache-from=myimage:latest-build -t myimage:latest-build
docker build . --cache-from=myimage:latest-build --cache-from=myimage:latest -t myimage:latest

docker push myimage:latest-build
docker push myimage:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this way, our application will always use the cache when building your images if it's available of course.&lt;br&gt;&lt;br&gt;
The first two lines will pull your previous builds images. The &lt;code&gt;... | true&lt;/code&gt; makes that commands always returning the 0 code for shell, even if the image doesn't exist that happens during the very first build. Another return code may stop your pipeline.&lt;/p&gt;
&lt;h2&gt;
  
  
  Image for local environment
&lt;/h2&gt;

&lt;p&gt;The next thing that I had a problem with, was creating a single Dockerfile for local and running environments. The answer to my need was just to add another stage in Dockerfile.&lt;/p&gt;

&lt;p&gt;I'm using the docker-compose to create the local development environment to work on my applications. In my case I had two Dockerfiles in my source code. The first of the files was the PHP with all extensions required to run the app. The second one was the runtime image that contains configured PHP same as the previous one, but with my application code installed in it. The PHP with extensions was the common part of both of those images. It's stupid to make the same changes in two separated Dockerfiles like I had to do it.&lt;/p&gt;

&lt;p&gt;There is a way to solve this problem using stages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;php:7.4.25-fpm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;apt update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    libicu-dev&lt;span class="o"&gt;=&lt;/span&gt;67.1-7 &lt;span class="se"&gt;\
&lt;/span&gt;    libgd-dev&lt;span class="o"&gt;=&lt;/span&gt;2.3.0-2 &lt;span class="se"&gt;\
&lt;/span&gt;    libonig-dev&lt;span class="o"&gt;=&lt;/span&gt;6.9.6-1.1 &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nv"&gt;unzip&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;6.0-26 &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    apt purge &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="nt"&gt;--auto-remove&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;docker-php-ext-install &lt;span class="se"&gt;\
&lt;/span&gt;    exif &lt;span class="se"&gt;\
&lt;/span&gt;    gd &lt;span class="se"&gt;\
&lt;/span&gt;    intl &lt;span class="se"&gt;\
&lt;/span&gt;    mbstring &lt;span class="se"&gt;\
&lt;/span&gt;    mysqli &lt;span class="se"&gt;\
&lt;/span&gt;    opcache &lt;span class="se"&gt;\
&lt;/span&gt;    pdo_mysql &lt;span class="se"&gt;\
&lt;/span&gt;    sockets

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;composer:2.1.11&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;build&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; composer.json .&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; composer.lock .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;composer &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--no-dev&lt;/span&gt; &lt;span class="nt"&gt;--no-scripts&lt;/span&gt; &lt;span class="nt"&gt;--ignore-platform-reqs&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;composer dumpautoload &lt;span class="nt"&gt;--optimize&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;final&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=build /app /app&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first change is to move the part that prepares my base PHP image and name this target as &lt;code&gt;base&lt;/code&gt;. The next part is the same as before. The last part is the target created from the &lt;code&gt;base&lt;/code&gt; stage result, named &lt;code&gt;final&lt;/code&gt;, that also copies our application from the &lt;code&gt;build&lt;/code&gt; stage.&lt;br&gt;&lt;br&gt;
Now to build this image for your running environment like production, you need to build the &lt;code&gt;final&lt;/code&gt; stage, which automatically builds all previous stages. For using that Dockerfile in your docker-compose you need to specify which stage you need to run your local environment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;web&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;#...&lt;/span&gt;
    &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;8080:80&lt;/span&gt;

  &lt;span class="na"&gt;php&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
      &lt;span class="na"&gt;dockerfile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Dockerfile&lt;/span&gt;
      &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;base&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;9000"&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.:/app&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And since now, we have had the same Dockerfile for the local and running environments.&lt;/p&gt;

&lt;p&gt;So that's all what I want to show you in this article.&lt;br&gt;&lt;br&gt;
Have a nice day, and keep taking care of your Dockerfiles 😃&lt;/p&gt;

&lt;p&gt;Originally posted on &lt;a href="https://mateuszcholewka.com/post/dockerfile-refactoring/" rel="noopener noreferrer"&gt;mateuszcholewka.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>devops</category>
      <category>docker</category>
      <category>php</category>
      <category>dockercompose</category>
    </item>
    <item>
      <title>Here are the Dockerfile tips you can apply to get your builds faster and safer</title>
      <dc:creator>Mateusz Cholewka</dc:creator>
      <pubDate>Sat, 13 Nov 2021 02:58:15 +0000</pubDate>
      <link>https://forem.com/mtk3d/here-are-the-dockerfile-tips-you-can-use-to-get-your-builds-faster-and-safer-4o1a</link>
      <guid>https://forem.com/mtk3d/here-are-the-dockerfile-tips-you-can-use-to-get-your-builds-faster-and-safer-4o1a</guid>
      <description>&lt;p&gt;Nowadays we are using docker a lot in web development. It's easy to use, great in scaling, and gives us an immutable environment for running your application from local development to deploy on production.&lt;br&gt;
To get the best experience with docker you should apply some practices to get fast and light builds of your docker images.&lt;/p&gt;

&lt;p&gt;In this article, I want to show you some of those practices based on this example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; php:7-fpm&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;

&lt;span class="k"&gt;ADD&lt;/span&gt;&lt;span class="s"&gt; https://deb.nodesource.com/setup_12.x .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;bash setup_12.x

&lt;span class="k"&gt;RUN &lt;/span&gt;curl &lt;span class="nt"&gt;-sS&lt;/span&gt; https://getcomposer.org/installer | php &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;--install-dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/usr/local/bin/ &lt;span class="nt"&gt;--filename&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;composer

&lt;span class="k"&gt;RUN &lt;/span&gt;apt update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    curl &lt;span class="se"&gt;\
&lt;/span&gt;    git &lt;span class="se"&gt;\
&lt;/span&gt;    htop &lt;span class="se"&gt;\
&lt;/span&gt;    libicu-dev &lt;span class="se"&gt;\
&lt;/span&gt;    libgd-dev &lt;span class="se"&gt;\
&lt;/span&gt;    mariadb-client &lt;span class="se"&gt;\
&lt;/span&gt;    libonig-dev &lt;span class="se"&gt;\
&lt;/span&gt;    vim &lt;span class="se"&gt;\
&lt;/span&gt;    unzip &lt;span class="se"&gt;\
&lt;/span&gt;    nodejs

&lt;span class="k"&gt;RUN &lt;/span&gt;apt purge &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="nt"&gt;--auto-remove&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; yarn

&lt;span class="k"&gt;RUN &lt;/span&gt;docker-php-ext-install &lt;span class="se"&gt;\
&lt;/span&gt;    exif &lt;span class="se"&gt;\
&lt;/span&gt;    gd &lt;span class="se"&gt;\
&lt;/span&gt;    intl &lt;span class="se"&gt;\
&lt;/span&gt;    mbstring &lt;span class="se"&gt;\
&lt;/span&gt;    mysqli &lt;span class="se"&gt;\
&lt;/span&gt;    opcache &lt;span class="se"&gt;\
&lt;/span&gt;    pdo_mysql &lt;span class="se"&gt;\
&lt;/span&gt;    sockets

&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; COMPOSER_ALLOW_SUPERUSER 1&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;composer &lt;span class="nb"&gt;install&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;yarn &lt;span class="nb"&gt;install&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;yarn run build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Base your builds on specific image version
&lt;/h2&gt;

&lt;p&gt;The first thing to change is the base image tag. As you can see in this Dockerfile the PHP7 is used, but the tag name is not precise enough. Here is the first improvement that we can make.&lt;/p&gt;

&lt;p&gt;When you are using dependencies managers like yarn / composer, you probably use the lock files. Using them will keep exactly the same version of dependencies on every install. So why don't do it with all dependencies?&lt;/p&gt;

&lt;p&gt;So the first dependency is the image tag we base our image on.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; php:7-fpm&lt;/span&gt;
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can change it to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; php:7.4.25-fpm&lt;/span&gt;
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That should save you for situations where your image doesn't work after a few months because of differences in newer PHP versions.&lt;/p&gt;

&lt;h2&gt;
  
  
  COPY your code last
&lt;/h2&gt;

&lt;p&gt;Docker images are built from layers. Every layer can be cached, and this cache can be reused for the next builds if nothing has been changed. Docker can use cache only if all of the previous layers are loaded from cache too.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;...
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . /app/&lt;/span&gt;
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should order your build steps by frequency of changes. Your application code is probably the thing that is changing most often, so you should put it as late as possible.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; php:7.4.25-fpm&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="c"&gt;## remove COPY from here&lt;/span&gt;
...
## rest of commands
...
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="c"&gt;## final commands&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Do not use ADD for remote dependencies
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;ADD&lt;/code&gt; instruction in Dockerfile allows you to copy files from remote locations by URLs. This feature also can unpack the zip archives which is great, but it has one problem. It doesn't cache your files.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;ADD&lt;/span&gt;&lt;span class="s"&gt; https://deb.nodesource.com/setup_12.x ./node_setup.bash&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;bash node_setup.bash &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nb"&gt;rm &lt;/span&gt;node_setup.bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ok, that's better.&lt;br&gt;&lt;br&gt;
The setup script file is undesirable, so it can be removed after the installation. But the problem is that the layers in Dockerfiles works like commits in git. When you put something to the repository using commit you can delete it with the next one, but because git works incrementally, both versions are kept in history, and the repository size increases.&lt;br&gt;
To avoid this in docker images, you should create and remove undesirable files in the same instruction.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;RUN &lt;/span&gt;curl &lt;span class="nt"&gt;-sS&lt;/span&gt; https://deb.nodesource.com/setup_12.x ./node_setup.bash &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    bash node_setup.bash &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nb"&gt;rm &lt;/span&gt;node_setup.bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Better, but still not the best.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;RUN &lt;/span&gt;curl &lt;span class="nt"&gt;-sS&lt;/span&gt; https://deb.nodesource.com/setup_12.x ./node_setup.bash | bash -
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can do all that things in one-line command using a pipe. In this example, the file content will be fetched and pushed directly to the bash that will execute it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using composer in Dockerfile
&lt;/h2&gt;

&lt;p&gt;Here we have the composer installed in our container. It will be kept for all environments. It's not the best idea to keep it in final image, because it's not necessery and may add some vulnerabilities. There is a better option to use composer with multistage build that I want to describe in the next article.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;...
&lt;span class="k"&gt;RUN &lt;/span&gt;curl &lt;span class="nt"&gt;-sS&lt;/span&gt; https://getcomposer.org/installer | php &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;--install-dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/usr/local/bin/ &lt;span class="nt"&gt;--filename&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;composer
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This line is ok it will be cached, and do not leave any garbage.&lt;br&gt;
Maybe we should use the hash checking script that you can find in the official install script.&lt;br&gt;
You can also use this trick:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;...
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=composer:2.1.11 /usr/bin/composer /usr/bin/composer&lt;/span&gt;
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That will copy the composer bin from the external official composer image.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installing apt packages
&lt;/h2&gt;

&lt;p&gt;Next, we have some packages installed using apt manager. Let's check if all of them are needed.&lt;br&gt;&lt;br&gt;
The git may be required for pulling packages or building some binaries from source. I can't see any reason to keep it. Let's remove it for now.&lt;br&gt;&lt;br&gt;
The htop may be useful for debugging, but not for the final image, we can install it when we really need it. Vim is useless too because you shouldn't make any changes in the working container. It's stateless, so your changes will disappear on a restart. Also mariadb-client is probably required only for development.&lt;/p&gt;

&lt;p&gt;The rest of the packages may be required, but there is one more problem. The docker is using layers for caching. Every layer is built from dingle instruction. The cache is invalidated if the instruction or previous instruction had changed. So in this case if you do not change this instruction, the newer packages could be never installed, and they may vary depends on build environment.&lt;br&gt;&lt;br&gt;
If you add a specific version of every package, you will be sure that every image built from this Dockerfile has the same versions of packages, and the cache will be invalidated correctly.&lt;br&gt;&lt;br&gt;
You can do this by specifying the version after the &lt;code&gt;=&lt;/code&gt; sign. To check which version you need to install, go to your current working container, or to the container that you build your image from, and check it with a list command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;apt list libonig-dev
Listing... Done
libonig-dev/stable,now 6.9.6-1.1 amd64 &lt;span class="o"&gt;[&lt;/span&gt;installed]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example the currently working version is &lt;code&gt;5.5.9999+default&lt;/code&gt;, so let's check the rest and specify them.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;RUN &lt;/span&gt;apt update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    libicu-dev&lt;span class="o"&gt;=&lt;/span&gt;67.1-7 &lt;span class="se"&gt;\
&lt;/span&gt;    libgd-dev&lt;span class="o"&gt;=&lt;/span&gt;2.3.0-2 &lt;span class="se"&gt;\
&lt;/span&gt;    libonig-dev&lt;span class="o"&gt;=&lt;/span&gt;6.9.6-1.1 &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nv"&gt;unzip&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;6.0-26 &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nv"&gt;nodejs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;12.22.7-deb-1nodesource1

&lt;span class="k"&gt;RUN &lt;/span&gt;apt purge &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="nt"&gt;--auto-remove&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Of course, you need to keep them up to date manually. It's good to check them frequently.&lt;/p&gt;

&lt;p&gt;There is one more thing to do. After the install command, there is a commend that's cleaning your system after installing instruction. It's very good that that is here, but this is done in the separated instruction. As we remember, if we remove something on another layer, that will still exist in the previous layers of our final image. So let's do the cleaning in the same command. That should decrease your final image size.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;RUN &lt;/span&gt;apt update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    libicu-dev&lt;span class="o"&gt;=&lt;/span&gt;67.1-7 &lt;span class="se"&gt;\
&lt;/span&gt;    libgd-dev&lt;span class="o"&gt;=&lt;/span&gt;2.3.0-2 &lt;span class="se"&gt;\
&lt;/span&gt;    libonig-dev&lt;span class="o"&gt;=&lt;/span&gt;6.9.6-1.1 &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nv"&gt;unzip&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;6.0-26 &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nv"&gt;nodejs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;12.22.7-deb-1nodesource1 &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    apt purge &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="nt"&gt;--auto-remove&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Composer dependencies
&lt;/h2&gt;

&lt;p&gt;Let's get to the next lines. There is another one &lt;code&gt;RUN&lt;/code&gt; instruction, that will install all of our composer dependencies. The first thing that is missed here is that we install all dependencies also with dev dependencies, that's are not necessary for the running environment. So let's put some flags here.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;RUN &lt;/span&gt;composer &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--optimize-autoloader&lt;/span&gt; &lt;span class="nt"&gt;--no-dev&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Those flags will install all dependencies exclude dev, with autoloader optimization.&lt;/p&gt;

&lt;p&gt;As you remember, we have to move the &lt;code&gt;COPY&lt;/code&gt; instruction of our code from the beginning of this file as much as possible at the end. Here is the line where we need our project files. But do we need our entire codebase? How often do you change the dependencies in your project? For sure less often than your application code. So do we need to pull our dependencies every time when we change something in our code? Probably no 😃&lt;br&gt;&lt;br&gt;
So the only files that we need are the composer files there.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; composer.json .&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; composer.lock .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;composer &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--no-dev&lt;/span&gt; &lt;span class="nt"&gt;--no-scripts&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the cache will work for our composer depenencies.&lt;/p&gt;

&lt;h2&gt;
  
  
  The code
&lt;/h2&gt;

&lt;p&gt;Ok, it's time when we need our code because there are the build steps. Let's paste our &lt;code&gt;COPY&lt;/code&gt; instruction from the beginning here.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And now, we need to generate the autoloader file with all our project files&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;RUN &lt;/span&gt;composer dumpautoload &lt;span class="nt"&gt;--optimize&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Node dependencies
&lt;/h2&gt;

&lt;p&gt;For a node there is the same situation as in composer. So first copy packages files and next install all dependencies.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;RUN &lt;/span&gt;yarn &lt;span class="nb"&gt;install&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;yarn run build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do we need all dependencies or only non-dev dependencies? Maybe we don't need any node dependencies in the container because we use it only to build our frontend. So why not install everything and remove it after the build?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;RUN &lt;/span&gt;yarn &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    yarn run build &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; node_modules &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    yarn cache clean
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And right now, we have no node dependencies that are not necessary. The problem here is that we cannot cache those dependencies. There are two ways to resolve this problem. The first one is the multistage build, but it's the topic for another article, which will be available soon. The second option will be to move entire frontend building to the nginx Dockerfile.&lt;/p&gt;

&lt;h2&gt;
  
  
  Values for now
&lt;/h2&gt;

&lt;p&gt;Applying all those changes, let's check how much build process time we get.&lt;br&gt;&lt;br&gt;
Old image build 4m28s* 901MB&lt;br&gt;&lt;br&gt;
New image build 3m57s* 711MB&lt;br&gt;&lt;br&gt;
So we safe almost 200MB for final image. Our build time is not much better than before, but let's check how our cache is working now:&lt;br&gt;&lt;br&gt;
Old image with cache 4m35s*&lt;br&gt;&lt;br&gt;
New image with cache 25.1s*&lt;br&gt;&lt;br&gt;
So yea, the cache is working better for our new image.  &lt;/p&gt;
&lt;h2&gt;
  
  
  Do you really need node for running PHP application?
&lt;/h2&gt;

&lt;p&gt;In our example Dockerfile we are building our frontend app in the backend container, and then copy it to our frontend container:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; nginx:latest&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=backend /app/public /app/public&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; docker/nginx/default.conf /etc/nginx/default.conf&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why to not build our app directly in the frontend image.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; nginx:1.21.4&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; docker/nginx/default.conf /etc/nginx/default.conf&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;curl &lt;span class="nt"&gt;-sS&lt;/span&gt; https://deb.nodesource.com/setup_12.x ./node_setup.bash | bash -

&lt;span class="k"&gt;RUN &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;&lt;span class="nv"&gt;nodejs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;12.22.7-deb-1nodesource1 &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    apt purge &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="nt"&gt;--auto-remove&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; yarn

&lt;span class="k"&gt;RUN &lt;/span&gt;yarn &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    yarn run build &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; node_modules &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    yarn cache clean
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And our backend Dockerfile&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; php:7.4.25-fpm&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=composer:2.1.11 /usr/bin/composer /usr/bin/composer&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;apt update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    libicu-dev&lt;span class="o"&gt;=&lt;/span&gt;67.1-7 &lt;span class="se"&gt;\
&lt;/span&gt;    libgd-dev&lt;span class="o"&gt;=&lt;/span&gt;2.3.0-2 &lt;span class="se"&gt;\
&lt;/span&gt;    libonig-dev&lt;span class="o"&gt;=&lt;/span&gt;6.9.6-1.1 &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nv"&gt;unzip&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;6.0-26 &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    apt purge &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="nt"&gt;--auto-remove&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;docker-php-ext-install &lt;span class="se"&gt;\
&lt;/span&gt;    exif &lt;span class="se"&gt;\
&lt;/span&gt;    gd &lt;span class="se"&gt;\
&lt;/span&gt;    intl &lt;span class="se"&gt;\
&lt;/span&gt;    mbstring &lt;span class="se"&gt;\
&lt;/span&gt;    mysqli &lt;span class="se"&gt;\
&lt;/span&gt;    opcache &lt;span class="se"&gt;\
&lt;/span&gt;    pdo_mysql &lt;span class="se"&gt;\
&lt;/span&gt;    sockets

&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; COMPOSER_ALLOW_SUPERUSER 1&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; composer.json .&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; composer.lock .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;composer &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--no-dev&lt;/span&gt; &lt;span class="nt"&gt;--no-scripts&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;composer dumpautoload &lt;span class="nt"&gt;--optimize&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So right now our backend image without cache is building in 3m8s* and with cache in 6s*, and it weight is 597MB.&lt;br&gt;&lt;br&gt;
The frontend image is building 57s* and it weight is 310MB.&lt;br&gt;&lt;br&gt;
You can build them in parallel, so the final time could be the maximum time for one of the images.  &lt;/p&gt;

&lt;h2&gt;
  
  
  Multistage builds
&lt;/h2&gt;

&lt;p&gt;All of those changes may be even better with using feature called multistage builds.&lt;br&gt;
This topic should be available soon in the next article on my blog 😃&lt;/p&gt;

&lt;p&gt;Edit: &lt;a href="https://dev.to/mtk3d/read-this-before-you-start-using-the-multistage-builds-for-your-docker-images-21e7"&gt;It's now available&lt;/a&gt; &lt;/p&gt;




&lt;p&gt;*All the times that appear in this article, I got on my Mac with intel i5 and 16GB RAM environment.&lt;/p&gt;

&lt;p&gt;Please remember about using non root user in your docker images.&lt;/p&gt;

&lt;p&gt;Originally posted on &lt;a href="https://mateuszcholewka.com/post/dockerfile-refactoring/" rel="noopener noreferrer"&gt;mateuszcholewka.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>devops</category>
      <category>docker</category>
      <category>php</category>
      <category>javascript</category>
    </item>
    <item>
      <title>How to configure PHP logs for Docker</title>
      <dc:creator>Mateusz Cholewka</dc:creator>
      <pubDate>Sun, 07 Nov 2021 14:06:06 +0000</pubDate>
      <link>https://forem.com/mtk3d/how-to-configure-php-logs-for-docker-2384</link>
      <guid>https://forem.com/mtk3d/how-to-configure-php-logs-for-docker-2384</guid>
      <description>&lt;p&gt;If you are using docker and cloud services to run your application live, you should manage your logs.&lt;br&gt;&lt;br&gt;
The most common method to store them is to put them in the text file. It's the default configuration for most backend frameworks. This option is ok if you run your application locally or on the VPS server for test.&lt;br&gt;&lt;br&gt;
When you run your application in a production environment, you should choose a better option to manage your logs. Almost every cloud has a tool for rotating logs or if not, you can use for example Grafana Loki or ELK stack. Those solutions are better because give you interfaces to rotate and search your logs. Also, you have easy access to them, you no need to connect to your server to review them.&lt;br&gt;&lt;br&gt;
If you are using Docker containers, and you running your application in cloud services, often they will be automatically writing the logs of your containers to tools like AWS CloudWatch or GCloud Stackdriver.  &lt;/p&gt;

&lt;p&gt;But first, you need to redirect your log streams to the output of the Docker container to be able to use them.&lt;/p&gt;
&lt;h2&gt;
  
  
  Linux streams
&lt;/h2&gt;

&lt;p&gt;Docker containers are running the Linux processes. In linux every running process has 3 streams, &lt;code&gt;STDIN&lt;/code&gt;, &lt;code&gt;STDOUT&lt;/code&gt;, &lt;code&gt;STDERR&lt;/code&gt;. &lt;code&gt;STDIN&lt;/code&gt; it's command input stream, that you can provide for ex. by your keyboard. &lt;code&gt;STDOUT&lt;/code&gt; is the stream where the running command may print the output. &lt;code&gt;STDERR&lt;/code&gt; is the standard error stream, but the name I think is a bit confusing, because it is basically intended for diagnostic output.  &lt;/p&gt;

&lt;p&gt;When you run the &lt;code&gt;docker logs [container]&lt;/code&gt; command in your terminal, you will see the output of &lt;code&gt;STDOUT&lt;/code&gt; and &lt;code&gt;STDERR&lt;/code&gt; streams. So our goal is to redirect our logs to one of those streams.  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.docker.com/config/containers/logging/" rel="noopener noreferrer"&gt;Official docker documentation page&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  PHP-FPM
&lt;/h2&gt;

&lt;p&gt;In PHP we are often running our application using the PHP-FPM (Process Manager). If you run your docker with FPM inside a docker container, and you run the &lt;code&gt;docker logs&lt;/code&gt; command, you should see the output with processed requests, or errors.  &lt;/p&gt;

&lt;p&gt;So the PHP-FPM is already writing its output to &lt;code&gt;STDOUT&lt;/code&gt;.&lt;br&gt;&lt;br&gt;
The PHP-FPM allow us to catch workers output and forward them to the &lt;code&gt;STDOUT&lt;/code&gt;. To do that we need to make sure that the FPM is configured properly. You can create new config file, and push it for example to the &lt;code&gt;/usr/local/etc/php-fpm.d/logging.conf&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[global]
error_log = /proc/self/fd/2

[www]
access.log = /proc/self/fd/2

catch_workers_output = yes
decorate_workers_output = no
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;error_log&lt;/code&gt; and &lt;code&gt;access.log&lt;/code&gt; parameters are configuration of streams of logs output.&lt;br&gt;&lt;br&gt;
The &lt;code&gt;catch_workers_output&lt;/code&gt; option is turning on the worker's output caching. The &lt;code&gt;decorate_workers_output&lt;/code&gt; is the option that turns off the output decoration. If you leave this option turned on, FPM will decorate your application output like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[21-Mar-2016 14:10:02] WARNING: [pool www] child 12 said into stdout: "[your log line]"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Remember that &lt;code&gt;decorate_workers_output&lt;/code&gt; option is available only for &lt;a href="https://www.php.net/manual/en/install.fpm.configuration.php#decorate-workers-output" rel="noopener noreferrer"&gt;PHP 7.3.0 and higher&lt;/a&gt;.  &lt;/p&gt;

&lt;p&gt;If you are using official docker php-fpm image, this configuration is already set in the &lt;code&gt;/usr/local/etc/php-fpm.d/docker.conf&lt;/code&gt; file, so you no need to do anything more 😎&lt;/p&gt;

&lt;h2&gt;
  
  
  PHP application configuration
&lt;/h2&gt;

&lt;p&gt;Right now everything that will be put to the stdout from PHP workers will be shown in our docker logs. But when logs are forwarded to that stream in PHP?  &lt;/p&gt;

&lt;p&gt;To write something to &lt;code&gt;STDIN&lt;/code&gt; on PHP level, we need to just write to the &lt;code&gt;php://stdout&lt;/code&gt; stream.  &lt;/p&gt;

&lt;p&gt;In the simplest way you can do this like that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="nb"&gt;file_put_contents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'php://stdout'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Hello world'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you execute this code in php cli, you will get the &lt;code&gt;Hello world&lt;/code&gt; text on the output.&lt;/p&gt;

&lt;p&gt;But it's not the optimal way to push your logs to the &lt;code&gt;STDOUT&lt;/code&gt;. Every modern framework should have a PSR-3 Logger. I think that the most popular now is the monolog, so I will show you how to configure it in Symfony, Laravel, and in pure usage.  &lt;/p&gt;

&lt;h2&gt;
  
  
  Monolog
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/Seldaek/monolog" rel="noopener noreferrer"&gt;Monolog&lt;/a&gt; is great library to handle logs in your application. It's easy and elastic in configuration.  &lt;/p&gt;

&lt;h3&gt;
  
  
  Basic monolog configuration
&lt;/h3&gt;

&lt;p&gt;If you are using monolog in your project with manual configuration, you need to configure handler in this way:&lt;/p&gt;

&lt;p&gt;(Modified documentation example)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Monolog\Logger&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Monolog\Handler\StreamHandler&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$log&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'stdout'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$log&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;pushHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;StreamHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'php://stdout'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Logger&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;DEBUG&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="nv"&gt;$log&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Foo'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You just need to configure StreamHandler, to write to the &lt;code&gt;php://stdout&lt;/code&gt; file.&lt;/p&gt;

&lt;h3&gt;
  
  
  Symfony
&lt;/h3&gt;

&lt;p&gt;Symfony Kernel since the Flex was provided, &lt;a href="https://symfony.com/blog/new-in-symfony-3-4-minimalist-psr-3-logger" rel="noopener noreferrer"&gt;is using minimalist PSR-3 logger&lt;/a&gt;, that logs everything to &lt;code&gt;php://stderr&lt;/code&gt; by default.  &lt;/p&gt;

&lt;p&gt;In Symfony, monolog as other components is configured in YAML files. So the same configuration will look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/packages/monolog.yaml&lt;/span&gt;
&lt;span class="na"&gt;monolog&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;handlers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;stdout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;stream&lt;/span&gt;
            &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;php://stdout"&lt;/span&gt;
            &lt;span class="na"&gt;level&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;debug&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Laravel
&lt;/h3&gt;

&lt;p&gt;Laravel use the arrays for configuration so the same thing will look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;# config/logging.php
&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Monolog\Handler\StreamHandler&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;'channels'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="s1"&gt;'stdout'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'driver'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'monolog'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'handler'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;StreamHandler&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'level'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'LOG_LEVEL'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'debug'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="s1"&gt;'with'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="s1"&gt;'stream'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'php://stdout'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  STDERR or STDOUT
&lt;/h2&gt;

&lt;p&gt;In some articles on the internet, you can read that someone uses stderr, and someone uses stdout streams to write logs there. Right now I cannot fin any reasons to choose one of them which is better.&lt;br&gt;&lt;br&gt;
The only information that I found on this topic is &lt;a href="https://stackoverflow.com/questions/4919093/should-i-log-messages-to-stderr-or-stdout" rel="noopener noreferrer"&gt;that post&lt;/a&gt;.&lt;br&gt;&lt;br&gt;
I think that stderr is more popular in some examples, also Fabien Potencier set it as default in his minimalistic logger, so I think we can assume that this one is better.  &lt;/p&gt;

&lt;p&gt;Personally, I always used the stdout, so that's the reason why I use it in this post's examples.  If I will find a great reason for using one of them over another I will update this post.  &lt;/p&gt;

&lt;p&gt;Originally posted on &lt;a href="https://mateuszcholewka.com/post/php-logs-in-docker/" rel="noopener noreferrer"&gt;https://mateuszcholewka.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>php</category>
      <category>docker</category>
      <category>laravel</category>
      <category>symfony</category>
    </item>
  </channel>
</rss>
