<?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: Flemming</title>
    <description>The latest articles on Forem by Flemming (@flemming).</description>
    <link>https://forem.com/flemming</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%2F464394%2F3adba2b0-932a-4c37-94a8-1509b3eebcc3.jpeg</url>
      <title>Forem: Flemming</title>
      <link>https://forem.com/flemming</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/flemming"/>
    <language>en</language>
    <item>
      <title>Laravel with NGINX Unit</title>
      <dc:creator>Flemming</dc:creator>
      <pubDate>Wed, 13 Dec 2023 20:44:10 +0000</pubDate>
      <link>https://forem.com/flemming/laravel-with-nginx-unit-f57</link>
      <guid>https://forem.com/flemming/laravel-with-nginx-unit-f57</guid>
      <description>&lt;p&gt;In this brief tutorial, I'll provide a quick overview of how I hosted a small Laravel application using the new lightweight Nginx Unit Server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why?
&lt;/h2&gt;

&lt;p&gt;My goal was to host a Laravel application in Docker without the overhead of managing nginx and PHP-FPM. While I could use the built-in command &lt;code&gt;php artisan serve&lt;/code&gt;, it's not as performant as PHP-FPM. Therefore, I decided to utilize the new &lt;a href="https://unit.nginx.org/"&gt;Unit&lt;/a&gt; Server from Nginx.&lt;/p&gt;

&lt;h2&gt;
  
  
  How?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The configuration file
&lt;/h3&gt;

&lt;p&gt;Unit is configured via a handy JSON file. This file allows me to define the type of application to be executed and the location of static files.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;listeners&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;*:80&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pass&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;routes&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;routes&lt;/span&gt;&lt;span class="dl"&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;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;match&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;uri&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;!/index.php&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;action&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;share&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/var/www/html/public$uri&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;response_headers&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Cache-Control&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;max-age=60, s-maxage=120&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
                &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fallback&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pass&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;applications/laravel&lt;/span&gt;&lt;span class="dl"&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;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;applications&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;laravel&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;php&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;root&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/var/www/html/public/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;script&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;index.php&lt;/span&gt;&lt;span class="dl"&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;ol&gt;
&lt;li&gt;Initially, I defined a listener on a specific port.&lt;/li&gt;
&lt;li&gt;This listener directs all requests, checking if the requested asset is static; otherwise, it falls back to the application block.&lt;/li&gt;
&lt;li&gt;In the application config, I defined the language to be used and the script's location for execution.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Dockerfile
&lt;/h3&gt;

&lt;p&gt;Fortunately, Unit provides prebuilt Docker images that I utilized in the following steps.&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; unit:php8.2&lt;/span&gt;

&lt;span class="c"&gt;# Prepare workfir&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /var/www/html
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /var/www/html/databasestore
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /var/www/html&lt;/span&gt;

&lt;span class="c"&gt;# Pullin build artifacts from build stage&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=build --chown=unit:unit /var/www/html /var/www/html&lt;/span&gt;

&lt;span class="c"&gt;# Set timezone&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; TZ=Europe/Berlin&lt;/span&gt;

&lt;span class="c"&gt;# Update package lists and install required dependencies&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get &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="se"&gt;\
&lt;/span&gt;    libjpeg-dev &lt;span class="se"&gt;\
&lt;/span&gt;    libpng-dev &lt;span class="se"&gt;\
&lt;/span&gt;    libfreetype6-dev &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nb"&gt;sudo&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get clean &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /var/lib/apt/lists/&lt;span class="k"&gt;*&lt;/span&gt;

&lt;span class="c"&gt;# Install and enable the intl and gd extensions&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;docker-php-ext-install intl gd

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; docker/config.json /docker-entrypoint.d/config.json&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; docker/entrypoint.sh /docker-entrypoint.d/entrypoint.sh&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x /docker-entrypoint.d/entrypoint.sh

&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 80&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;1. Initially, I set up everything, including creating the workspace, setting the timezone, and installing required extensions.&lt;/li&gt;
&lt;li&gt;The most crucial tasks are the last two &lt;code&gt;COPY&lt;/code&gt; commands, placing &lt;code&gt;config.json&lt;/code&gt; and &lt;code&gt;entrypoint.sh&lt;/code&gt; in &lt;code&gt;/docker-entrypoint.d&lt;/code&gt;. Unit will reconfigure itself using &lt;code&gt;config.json&lt;/code&gt; and then execute &lt;code&gt;entrypoint.sh&lt;/code&gt;, which migrates the database, flushes the config, etc.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now, I have a relatively compact image that serves my Laravel application much more efficiently than the built-in serve command. It's easier to configure and has less overhead.&lt;/p&gt;

&lt;p&gt;You can find an example repository here: &lt;a href="https://github.com/chaostreff-flensburg/simple-borrow"&gt;https://github.com/chaostreff-flensburg/simple-borrow&lt;/a&gt;&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>nginx</category>
      <category>php</category>
      <category>docker</category>
    </item>
    <item>
      <title>Development environment with Docker and Traefik</title>
      <dc:creator>Flemming</dc:creator>
      <pubDate>Thu, 26 Nov 2020 15:14:01 +0000</pubDate>
      <link>https://forem.com/flemming/development-enviroment-with-docker-and-traefik-1lg6</link>
      <guid>https://forem.com/flemming/development-enviroment-with-docker-and-traefik-1lg6</guid>
      <description>&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;Local development with docker is nothing new.&lt;br&gt;
Run a local container export some ports and here we go.&lt;br&gt;
But I ran into the problem of working with multiple projects at the same time of conflicting ports on my docker host. And it is hard to remember which project is running on which port. Local domains would be a nice solution.&lt;br&gt;
I will walk you through my local development setup with docker and traefik to solve this problem.&lt;/p&gt;
&lt;h2&gt;
  
  
  Concept
&lt;/h2&gt;

&lt;p&gt;We will install a reverse proxy on our local machine to add a domain to our projects. We will do this by using Traefik (&lt;a href="https://traefik.io/traefik" rel="noopener noreferrer"&gt;https://traefik.io/traefik&lt;/a&gt;).&lt;br&gt;
Traefik calls itself a cloud native application proxy.&lt;br&gt;
It is ideal for using in cloud context like Kubernetes or docker. Traefik itself is also a simple docker container. And this will be the only container to expose a port to our docker host. The containers of the different projects and the traefik container will be in the same docker network. Traefik will forward the requests from the client to the corresponding container.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Foxq2hx461j78ndvzqq38.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Foxq2hx461j78ndvzqq38.jpg" alt="Concept skatch" width="561" height="573"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Requirements
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;docker&lt;/li&gt;
&lt;li&gt;docker-compose&lt;/li&gt;
&lt;li&gt;your IDE of choice&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;

&lt;p&gt;Our first step will be to create a docker network.&lt;br&gt;
We will call it "web". We are creating this network so that different docker-compose stacks can connect to each other.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker network create web
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we are starting our traefik container.&lt;br&gt;
We could do this by running a simple docker command, but in this case ware a using a small docker-compose file to configure our container.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;version: '3'

networks:
  web:
    external: true

services:
  traefik:
    image: traefik:v2.3
    command:
      - "--log.level=DEBUG"
      - "--api.insecure=true"
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--entrypoints.web.address=:80"
    restart: always
    ports:
      - "80:80"
      - "8080:8080" # The Web UI (enabled by --api)
    networks:
      - web
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save this file in a directory and start the container by typing&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker-compose up -d
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the container started successfully we can access the traefik dashboard via &lt;a href="http://localhost:8080" rel="noopener noreferrer"&gt;http://localhost:8080&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now we are starting a small web project. For example this is only a small website. I will only show the docker-compose.yml file in this post. You can find the complete folder structure and traefik setup here: &lt;a href="https://github.com/flemssound/local-dev-docker-traefik" rel="noopener noreferrer"&gt;https://github.com/flemssound/local-dev-docker-traefik&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;version: '3.3'

networks:
  web:
    external: true

services:
  application:
    image: nginx
    networks:
      - web
    # Here we define our settings for traefik how to proxy our service.
    labels:
      # This is enableing treafik to proxy this service
      - "traefik.enable=true"
      # Here we have to define the URL
      - "traefik.http.routers.myproject.rule=Host(`myproject.localhost`)"
      # Here we are defining wich entrypoint should be used by clients to access this service
      - "traefik.http.routers.myproject.entrypoints=web"
      # Here we define in wich network treafik can find this service
      - "traefik.docker.network=web"
      # This is the port that traefik should proxy
      - "traefik.http.services.myproject.loadbalancer.server.port=80"
    volumes:
      - ./html:/usr/share/nginx/html
    restart: always
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we can access our website via &lt;a href="http://myproject.localhost" rel="noopener noreferrer"&gt;http://myproject.localhost&lt;/a&gt;.&lt;br&gt;
Everything in the HTML folder is mounted to the public folder into the nginx container.&lt;br&gt;
Instead of exposing the nginx port directly to our host we proxy it through traefik.&lt;br&gt;
We can also see it in the traefik dashboard.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fpmhz3aw76g41b8j1s29e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fpmhz3aw76g41b8j1s29e.png" alt="Traefik dashboard" width="800" height="482"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To create another project copy the myproject folder, adjust the docker-compose.yml and start it up. Now we have a second project running, for example under mysecondproject.localhost also on port 80, and we don't have to worry about conflicting ports in our projects and can access them by their name.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Traefik: &lt;a href="https://traefik.io/traefik" rel="noopener noreferrer"&gt;https://traefik.io/traefik&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Nginx container: &lt;a href="https://hub.docker.com/_/nginx" rel="noopener noreferrer"&gt;https://hub.docker.com/_/nginx&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Example project and setup: &lt;a href="https://github.com/flemssound/local-dev-docker-traefik" rel="noopener noreferrer"&gt;https://github.com/flemssound/local-dev-docker-traefik&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>docker</category>
      <category>webdev</category>
      <category>tutorial</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
