{"id":1487,"date":"2025-12-29T14:00:34","date_gmt":"2025-12-29T17:00:34","guid":{"rendered":"https:\/\/rjsites.com.br\/?p=1487"},"modified":"2025-12-29T16:57:45","modified_gmt":"2025-12-29T19:57:45","slug":"docker-para-laravel-do-jeito-certo-nginx-php-8-3-mysql-redis-ambiente-profissional-de-desenvolvimento","status":"publish","type":"post","link":"https:\/\/rjsites.com.br\/index.php\/2025\/12\/29\/docker-para-laravel-do-jeito-certo-nginx-php-8-3-mysql-redis-ambiente-profissional-de-desenvolvimento\/","title":{"rendered":"Docker para Laravel do jeito certo: Nginx + PHP 8.3 + MySQL + Redis (ambiente profissional de desenvolvimento)"},"content":{"rendered":"\n<h4 class=\"wp-block-heading\">Sem gambiarra, sem permiss\u00f5es quebradas e entendendo o porqu\u00ea de cada decis\u00e3o.<\/h4>\n\n\n\n<h2 class=\"wp-block-heading\">Introdu\u00e7\u00e3o<\/h2>\n\n\n\n<p>Montar um ambiente Docker para Laravel <strong>parece simples<\/strong>, at\u00e9 voc\u00ea cair em problemas cl\u00e1ssicos como:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>erro de permiss\u00e3o em <code>storage<\/code><\/li>\n\n\n\n<li>PHP n\u00e3o se comunica com o Nginx<\/li>\n\n\n\n<li>Redis n\u00e3o conecta<\/li>\n\n\n\n<li>diferen\u00e7as entre Windows, WSL e Linux<\/li>\n<\/ul>\n\n\n\n<p>Neste tutorial, vou mostrar <strong>como montar um ambiente Docker profissional<\/strong> para rodar aplica\u00e7\u00f5es Laravel usando:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Nginx<\/strong><\/li>\n\n\n\n<li><strong>PHP 8.3 (FPM)<\/strong><\/li>\n\n\n\n<li><strong>MySQL<\/strong><\/li>\n\n\n\n<li><strong>Redis<\/strong><\/li>\n<\/ul>\n\n\n\n<p>Tudo funcionando corretamente e entendendo <strong>por que cada configura\u00e7\u00e3o existe<\/strong>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83d\udce6 Stack utilizada<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Docker + Docker Compose<\/li>\n\n\n\n<li>Nginx (alpine)<\/li>\n\n\n\n<li>PHP 8.3 FPM<\/li>\n\n\n\n<li>MySQL 8<\/li>\n\n\n\n<li>Redis<\/li>\n\n\n\n<li>Laravel<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83d\udcc1 Estrutura de pastas<\/h2>\n\n\n\n<pre class=\"wp-block-preformatted\">project-root\/<br>\u251c\u2500\u2500 docker\/<br>\u2502   \u251c\u2500\u2500 nginx\/<br>\u2502   \u2502   \u2514\u2500\u2500 default.conf<br>\u2502   \u2514\u2500\u2500 php\/<br>\u2502       \u2514\u2500\u2500 Dockerfile<br>\u251c\u2500\u2500 docker-compose.yml<br>\u251c\u2500\u2500 .env<br>\u2514\u2500\u2500 (projeto Laravel)<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83d\udc33 docker-compose.yml<\/h2>\n\n\n\n<p>Esse arquivo \u00e9 o <strong>cora\u00e7\u00e3o do ambiente<\/strong>.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">version: \"3.9\"<br><br>services:<br>  app:<br>    build:<br>      context: .\/docker\/php<br>    container_name: laravel_app<br>    volumes:<br>      - .:\/var\/www<br>    working_dir: \/var\/www<br>    command: &gt;<br>      sh -c \"<br>      chown -R www-data:www-data storage bootstrap\/cache &amp;&amp;<br>      chmod -R 775 storage bootstrap\/cache &amp;&amp;<br>      php-fpm<br>      \"<br>    depends_on:<br>      - mysql<br>      - redis<br>    networks:<br>      - laravel<br><br>  nginx:<br>    image: nginx:alpine<br>    container_name: laravel_nginx<br>    ports:<br>      - \"8000:80\"<br>    volumes:<br>      - .:\/var\/www<br>      - .\/docker\/nginx\/default.conf:\/etc\/nginx\/conf.d\/default.conf<br>    depends_on:<br>      - app<br>    networks:<br>      - laravel<br><br>  mysql:<br>    image: mysql:8.0<br>    container_name: laravel_mysql<br>    restart: always<br>    environment:<br>      MYSQL_DATABASE: laravel<br>      MYSQL_USER: laravel<br>      MYSQL_PASSWORD: laravel<br>      MYSQL_ROOT_PASSWORD: root<br>    ports:<br>      - \"3306:3306\"<br>    volumes:<br>      - mysql_data:\/var\/lib\/mysql<br>    networks:<br>      - laravel<br><br>  redis:<br>    image: redis:alpine<br>    container_name: laravel_redis<br>    ports:<br>      - \"6379:6379\"<br>    networks:<br>      - laravel<br><br>networks:<br>  laravel:<br>    driver: bridge<br><br>volumes:<br>  mysql_data:<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">\ud83d\udd0d Por que usamos <code>command<\/code> no servi\u00e7o <code>app<\/code>?<\/h3>\n\n\n\n<p>Porque <strong>as permiss\u00f5es do Laravel precisam ser tratadas em runtime<\/strong>, n\u00e3o no build da imagem.<\/p>\n\n\n\n<p>Isso evita erros cl\u00e1ssicos como:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">file_put_contents(): Permission denied<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83d\udc18 Dockerfile do PHP 8.3<\/h2>\n\n\n\n<p>\ud83d\udcc1 <code>docker\/php\/Dockerfile<\/code><\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">FROM php:8.3-fpm<br><br># Depend\u00eancias do sistema<br>RUN apt-get update &amp;&amp; apt-get install -y \\<br>    git \\<br>    curl \\<br>    zip \\<br>    unzip \\<br>    libpng-dev \\<br>    libjpeg-dev \\<br>    libfreetype6-dev \\<br>    libonig-dev \\<br>    libxml2-dev \\<br>    libzip-dev \\<br>    libsodium-dev \\<br>    libicu-dev \\<br>    libpq-dev \\<br>    &amp;&amp; docker-php-ext-configure gd --with-freetype --with-jpeg \\<br>    &amp;&amp; docker-php-ext-install \\<br>        pdo \\<br>        pdo_mysql \\<br>        mbstring \\<br>        exif \\<br>        pcntl \\<br>        bcmath \\<br>        gd \\<br>        zip \\<br>        opcache \\<br>        sodium \\<br>        intl \\<br>    &amp;&amp; apt-get clean \\<br>    &amp;&amp; rm -rf \/var\/lib\/apt\/lists\/*<br><br># Redis<br>RUN pecl install redis \\<br>    &amp;&amp; docker-php-ext-enable redis<br><br># Composer<br>COPY --from=composer:2 \/usr\/bin\/composer \/usr\/bin\/composer<br><br># Permiss\u00f5es<br>RUN chown -R www-data:www-data \/var\/www<br><br>WORKDIR \/var\/www<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83c\udf10 Configura\u00e7\u00e3o do Nginx<\/h2>\n\n\n\n<p>\ud83d\udcc1 <code>docker\/nginx\/default.conf<\/code><\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">server {<br>    listen 80;<br>    index index.php index.html;<br>    server_name localhost;<br>    root \/var\/www\/public;<br><br>    location \/ {<br>        try_files $uri $uri\/ \/index.php?$query_string;<br>    }<br><br>    location ~ \\.php$ {<br>        try_files $uri =404;<br>        fastcgi_pass app:9000;<br>        fastcgi_index index.php;<br>        include fastcgi_params;<br>        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;<br>        fastcgi_param PATH_INFO $fastcgi_path_info;<br>    }<br><br>    location ~ \/\\.ht {<br>        deny all;<br>    }<br>}<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">\u2699\ufe0f Configura\u00e7\u00e3o do Laravel (.env)<\/h2>\n\n\n\n<pre class=\"wp-block-preformatted\">DB_CONNECTION=mysql<br>DB_HOST=mysql<br>DB_PORT=3306<br>DB_DATABASE=laravel<br>DB_USERNAME=laravel<br>DB_PASSWORD=laravel<br><br>REDIS_CLIENT=phpredis<br>REDIS_HOST=redis<br>REDIS_PASSWORD=null<br>REDIS_PORT=6379<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83d\ude80 Subindo o ambiente<\/h2>\n\n\n\n<pre class=\"wp-block-preformatted\">docker compose up -d --build<\/pre>\n\n\n\n<p>Depois:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">docker compose exec app php artisan key:generate<br>docker compose exec app php artisan migrate<\/pre>\n\n\n\n<p>Acesse:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">http:\/\/localhost:8000<\/pre>\n\n\n\n<p>\u2705 Laravel rodando<br>\u2705 PHP 8.3<br>\u2705 MySQL<br>\u2705 Redis<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\u2757 Erro comum: permiss\u00f5es no Laravel (e por que N\u00c3O usar o Dockerfile)<\/h2>\n\n\n\n<p>Muitos tutoriais tentam resolver permiss\u00f5es no <code>Dockerfile<\/code>, mas isso <strong>n\u00e3o funciona<\/strong> quando usamos volumes.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">\u274c Erro comum no build<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>chmod: cannot access '\/var\/www\/storage': No such file or directory<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">\ud83d\udccc Por qu\u00ea?<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>O Dockerfile roda <strong>antes<\/strong> do volume ser montado<\/li>\n\n\n\n<li>As pastas do Laravel ainda n\u00e3o existem nesse momento<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">\u2705 Solu\u00e7\u00e3o correta<\/h3>\n\n\n\n<p>Tratar permiss\u00f5es <strong>no runtime<\/strong>, usando o <code>command<\/code> no <code>docker-compose.yml<\/code>, como fizemos acima.<\/p>\n\n\n\n<p>Essa abordagem \u00e9:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>mais limpa<\/li>\n\n\n\n<li>mais previs\u00edvel<\/li>\n\n\n\n<li>mais profissional<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity is-style-wide\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83e\udde0 Conclus\u00e3o<\/h2>\n\n\n\n<p>Com essa stack voc\u00ea tem:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Ambiente Docker confi\u00e1vel para Laravel<\/li>\n\n\n\n<li>PHP 8.3 pronto para produ\u00e7\u00e3o<\/li>\n\n\n\n<li>Redis configurado corretamente<\/li>\n\n\n\n<li>MySQL persistente<\/li>\n\n\n\n<li>Zero gambiarra de permiss\u00e3o<\/li>\n<\/ul>\n\n\n\n<p>Esse setup funciona bem em:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Windows + WSL<\/li>\n\n\n\n<li>Linux<\/li>\n\n\n\n<li>macOS<\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>Sem gambiarra, sem permiss\u00f5es quebradas e entendendo o porqu\u00ea de cada decis\u00e3o. Introdu\u00e7\u00e3o Montar um ambiente Docker para Laravel parece simples, at\u00e9 voc\u00ea cair em problemas cl\u00e1ssicos como: Neste tutorial, vou mostrar como montar um ambiente Docker profissional para rodar aplica\u00e7\u00f5es Laravel usando: Tudo funcionando corretamente e entendendo por que cada configura\u00e7\u00e3o existe. \ud83d\udce6 Stack [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"site-sidebar-layout":"default","site-content-layout":"","ast-site-content-layout":"default","site-content-style":"default","site-sidebar-style":"default","ast-global-header-display":"","ast-banner-title-visibility":"","ast-main-header-display":"","ast-hfb-above-header-display":"","ast-hfb-below-header-display":"","ast-hfb-mobile-header-display":"","site-post-title":"","ast-breadcrumbs-content":"","ast-featured-img":"","footer-sml-layout":"","theme-transparent-header-meta":"","adv-header-id-meta":"","stick-header-meta":"","header-above-stick-meta":"","header-main-stick-meta":"","header-below-stick-meta":"","astra-migrate-meta-layouts":"set","ast-page-background-enabled":"default","ast-page-background-meta":{"desktop":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"ast-content-background-meta":{"desktop":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"footnotes":""},"categories":[1],"tags":[],"class_list":["post-1487","post","type-post","status-publish","format-standard","hentry","category-sem-categoria"],"jetpack_featured_media_url":"","_links":{"self":[{"href":"https:\/\/rjsites.com.br\/index.php\/wp-json\/wp\/v2\/posts\/1487","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/rjsites.com.br\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/rjsites.com.br\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/rjsites.com.br\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/rjsites.com.br\/index.php\/wp-json\/wp\/v2\/comments?post=1487"}],"version-history":[{"count":4,"href":"https:\/\/rjsites.com.br\/index.php\/wp-json\/wp\/v2\/posts\/1487\/revisions"}],"predecessor-version":[{"id":1492,"href":"https:\/\/rjsites.com.br\/index.php\/wp-json\/wp\/v2\/posts\/1487\/revisions\/1492"}],"wp:attachment":[{"href":"https:\/\/rjsites.com.br\/index.php\/wp-json\/wp\/v2\/media?parent=1487"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/rjsites.com.br\/index.php\/wp-json\/wp\/v2\/categories?post=1487"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/rjsites.com.br\/index.php\/wp-json\/wp\/v2\/tags?post=1487"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}