{"id":436,"date":"2025-08-27T21:56:01","date_gmt":"2025-08-27T21:56:01","guid":{"rendered":"https:\/\/1v0.net\/blog\/?p=436"},"modified":"2025-08-27T21:56:03","modified_gmt":"2025-08-27T21:56:03","slug":"laravel-with-docker-sail-the-right-way","status":"publish","type":"post","link":"https:\/\/1v0.net\/blog\/laravel-with-docker-sail-the-right-way\/","title":{"rendered":"Laravel with Docker &#038; Sail: The Right Way"},"content":{"rendered":"\n<h2 class=\"wp-block-heading\"><strong>Laravel with Docker &amp; Sail: The Right Way<\/strong><\/h2>\n\n\n\n<p><strong>Laravel Sail<\/strong> is the official Docker development environment for Laravel. It gives you a ready-to-use Docker Compose setup with PHP, MySQL\/Postgres, Redis, Mailhog, and more. In this article, we\u2019ll walk step-by-step through setting up Sail, customizing services, adding extensions, debugging containers, and preparing for production. This is the \u201cright way\u201d to embrace Docker without losing Laravel\u2019s simplicity.<\/p>\n\n\n\n<div class=\"wp-block-spacer\" style=\"height:100px\" aria-hidden=\"true\"><\/div>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>1 \u2014 Install Sail<\/strong><\/h2>\n\n\n\n<p>Laravel Sail ships with new projects, but you can add it to any Laravel 12 app. Require it via Composer, then publish the <code>docker-compose.yml<\/code> file.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"Bash\" data-shcb-language-slug=\"bash\"><span><code class=\"hljs language-bash\"><span class=\"hljs-comment\"># in your Laravel app root<\/span>\ncomposer require laravel\/sail --dev\nphp artisan sail:install\n\n<span class=\"hljs-comment\"># start Sail with Docker Compose<\/span>\n.\/vendor\/bin\/sail up -d<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Bash<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">bash<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p><code>sail:install<\/code> lets you choose services (MySQL, Redis, Meilisearch, Mailhog, Selenium). The generated <code>docker-compose.yml<\/code> defines containers. Running <code>sail up -d<\/code> launches them in the background.<\/p>\n\n\n\n<div class=\"wp-block-spacer\" style=\"height:100px\" aria-hidden=\"true\"><\/div>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>2 \u2014 docker-compose.yml Overview<\/strong><\/h2>\n\n\n\n<p>The default file defines services like <code>laravel.test<\/code>, <code>mysql<\/code>, <code>redis<\/code>, <code>mailhog<\/code>. You can customize it (ports, volumes, versions).<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"YAML\" data-shcb-language-slug=\"yaml\"><span><code class=\"hljs language-yaml\"><span class=\"hljs-attr\">version:<\/span> <span class=\"hljs-string\">'3'<\/span>\n<span class=\"hljs-attr\">services:<\/span>\n  <span class=\"hljs-attr\">laravel.test:<\/span>\n    <span class=\"hljs-attr\">build:<\/span>\n      <span class=\"hljs-attr\">context:<\/span> <span class=\"hljs-string\">.\/vendor\/laravel\/sail\/runtimes\/8.3<\/span>\n    <span class=\"hljs-attr\">ports:<\/span>\n      <span class=\"hljs-bullet\">-<\/span> <span class=\"hljs-string\">'${APP_PORT:-80}:80'<\/span>\n    <span class=\"hljs-attr\">volumes:<\/span>\n      <span class=\"hljs-bullet\">-<\/span> <span class=\"hljs-string\">'.:\/var\/www\/html'<\/span>\n    <span class=\"hljs-attr\">environment:<\/span>\n      <span class=\"hljs-attr\">WWWGROUP:<\/span> <span class=\"hljs-string\">'${WWWGROUP}'<\/span>\n    <span class=\"hljs-attr\">depends_on:<\/span>\n      <span class=\"hljs-bullet\">-<\/span> <span class=\"hljs-string\">mysql<\/span>\n      <span class=\"hljs-bullet\">-<\/span> <span class=\"hljs-string\">redis<\/span>\n      <span class=\"hljs-bullet\">-<\/span> <span class=\"hljs-string\">mailhog<\/span>\n\n  <span class=\"hljs-attr\">mysql:<\/span>\n    <span class=\"hljs-attr\">image:<\/span> <span class=\"hljs-string\">'mysql:8.0'<\/span>\n    <span class=\"hljs-attr\">environment:<\/span>\n      <span class=\"hljs-attr\">MYSQL_DATABASE:<\/span> <span class=\"hljs-string\">'${DB_DATABASE}'<\/span>\n      <span class=\"hljs-attr\">MYSQL_USER:<\/span> <span class=\"hljs-string\">'${DB_USERNAME}'<\/span>\n      <span class=\"hljs-attr\">MYSQL_PASSWORD:<\/span> <span class=\"hljs-string\">'${DB_PASSWORD}'<\/span>\n      <span class=\"hljs-attr\">MYSQL_ROOT_PASSWORD:<\/span> <span class=\"hljs-string\">'${DB_PASSWORD}'<\/span>\n    <span class=\"hljs-attr\">ports:<\/span>\n      <span class=\"hljs-bullet\">-<\/span> <span class=\"hljs-string\">'3306:3306'<\/span>\n\n  <span class=\"hljs-attr\">redis:<\/span>\n    <span class=\"hljs-attr\">image:<\/span> <span class=\"hljs-string\">'redis:alpine'<\/span>\n    <span class=\"hljs-attr\">ports:<\/span>\n      <span class=\"hljs-bullet\">-<\/span> <span class=\"hljs-string\">'6379:6379'<\/span>\n\n  <span class=\"hljs-attr\">mailhog:<\/span>\n    <span class=\"hljs-attr\">image:<\/span> <span class=\"hljs-string\">'mailhog\/mailhog:latest'<\/span>\n    <span class=\"hljs-attr\">ports:<\/span>\n      <span class=\"hljs-bullet\">-<\/span> <span class=\"hljs-string\">'8025:8025'<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">YAML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">yaml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>This setup runs Laravel in <code>laravel.test<\/code> container, with MySQL, Redis, and Mailhog. Ports are mapped to your host for development: <code>http:\/\/localhost<\/code> for app, <code>8025<\/code> for Mailhog UI.<\/p>\n\n\n\n<div class=\"wp-block-spacer\" style=\"height:100px\" aria-hidden=\"true\"><\/div>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>3 \u2014 Customizing PHP &amp; Extensions<\/strong><\/h2>\n\n\n\n<p>You can add PHP extensions by editing the Sail runtime Dockerfile. Example: enabling <code>imagick<\/code>.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"Dockerfile\" data-shcb-language-slug=\"dockerfile\"><span><code class=\"hljs language-dockerfile\"><span class=\"hljs-comment\"># vendor\/laravel\/sail\/runtimes\/8.3\/Dockerfile<\/span>\n<span class=\"hljs-keyword\">FROM<\/span> laravelsail\/php83-composer\n\n<span class=\"hljs-comment\"># Install Imagick<\/span>\n<span class=\"hljs-keyword\">RUN<\/span><span class=\"bash\"> apt-get update &amp;&amp; apt-get install -y libmagickwand-dev --no-install-recommends \\\n    &amp;&amp; pecl install imagick \\\n    &amp;&amp; docker-php-ext-enable imagick<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Dockerfile<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">dockerfile<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Rebuild Sail after editing:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"Bash\" data-shcb-language-slug=\"bash\"><span><code class=\"hljs language-bash\">.\/vendor\/bin\/sail build --no-cache<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Bash<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">bash<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>This ensures your PHP container now has Imagick. You can repeat the same process for other system libs or extensions.<\/p>\n\n\n\n<div class=\"wp-block-spacer\" style=\"height:100px\" aria-hidden=\"true\"><\/div>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>4 \u2014 Running Artisan, Composer, and NPM<\/strong><\/h2>\n\n\n\n<p>Sail wraps Docker Compose commands. Instead of <code>php artisan<\/code>, prefix with <code>sail<\/code>. Same for Composer, NPM, PHPUnit.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"Bash\" data-shcb-language-slug=\"bash\"><span><code class=\"hljs language-bash\"><span class=\"hljs-comment\"># Artisan<\/span>\n.\/vendor\/bin\/sail artisan migrate\n\n<span class=\"hljs-comment\"># Composer<\/span>\n.\/vendor\/bin\/sail composer require spatie\/laravel-permission\n\n<span class=\"hljs-comment\"># NPM<\/span>\n.\/vendor\/bin\/sail npm run dev\n\n<span class=\"hljs-comment\"># Testing<\/span>\n.\/vendor\/bin\/sail <span class=\"hljs-built_in\">test<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Bash<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">bash<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>This ensures all commands run inside the PHP container, not on your host machine, so versions are consistent across the team.<\/p>\n\n\n\n<div class=\"wp-block-spacer\" style=\"height:100px\" aria-hidden=\"true\"><\/div>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>5 \u2014 Debugging Containers<\/strong><\/h2>\n\n\n\n<p>You can \u201cexec\u201d into running containers or check logs directly.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"Bash\" data-shcb-language-slug=\"bash\"><span><code class=\"hljs language-bash\"><span class=\"hljs-comment\"># enter PHP container shell<\/span>\n.\/vendor\/bin\/sail shell\n\n<span class=\"hljs-comment\"># check logs for app container<\/span>\n.\/vendor\/bin\/sail logs -f laravel.test<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Bash<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">bash<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p><code>sail shell<\/code> drops you into a bash session inside the PHP container, where you can run artisan tinker or inspect files. <code>sail logs -f<\/code> streams container logs (good for debugging queues or errors).<\/p>\n\n\n\n<div class=\"wp-block-spacer\" style=\"height:100px\" aria-hidden=\"true\"><\/div>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>6 \u2014 Preparing Sail for Production<\/strong><\/h2>\n\n\n\n<p>Sail is intended for development, but its Docker Compose setup can inspire production images. For production:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Build a lean PHP-FPM image with <code>composer install --no-dev<\/code> &amp; cached config\/routes\/views.<\/li>\n<li>Use Nginx as a separate container, not inside <code>laravel.test<\/code>.<\/li>\n<li>Use AWS RDS\/DO Managed DB instead of container DB.<\/li>\n<li>Use Redis container or managed ElastiCache\/DO Redis for queues (see <a href=\"\/blog\/how-to-use-laravel-queues-for-faster-performance\">#42 Queues<\/a>).<\/li>\n<li>Run Horizon in its own service (see <a href=\"\/blog\/how-to-use-laravel-horizon-for-queue-monitoring\">#45 Horizon<\/a>).<\/li>\n<\/ul>\n\n\n\n<p>For scaling beyond a single host, migrate to Kubernetes or ECS Fargate. See <a href=\"\/blog\/deploying-laravel-on-aws-complete-guide-2025\">#52 AWS Guide<\/a> for container deployments.<\/p>\n\n\n\n<div class=\"wp-block-spacer\" style=\"height:100px\" aria-hidden=\"true\"><\/div>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>7 \u2014 Developer UI: Sail Status Page<\/strong><\/h2>\n\n\n\n<p>For teams new to Docker, a tiny UI can display which services are running inside Sail. This helps onboarding without needing to know Docker commands.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\">\/\/ routes\/web.php<\/span>\nRoute::get(<span class=\"hljs-string\">'\/sail-status'<\/span>, <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-params\">()<\/span> <\/span>{\n    $services = &#91;\n        <span class=\"hljs-string\">'MySQL'<\/span> =&gt; env(<span class=\"hljs-string\">'DB_HOST'<\/span>).<span class=\"hljs-string\">':'<\/span>.env(<span class=\"hljs-string\">'DB_PORT'<\/span>),\n        <span class=\"hljs-string\">'Redis'<\/span> =&gt; env(<span class=\"hljs-string\">'REDIS_HOST'<\/span>).<span class=\"hljs-string\">':'<\/span>.env(<span class=\"hljs-string\">'REDIS_PORT'<\/span>),\n        <span class=\"hljs-string\">'Mailhog'<\/span> =&gt; <span class=\"hljs-string\">'http:\/\/localhost:8025'<\/span>\n    ];\n    <span class=\"hljs-keyword\">return<\/span> view(<span class=\"hljs-string\">'sail.status'<\/span>, compact(<span class=\"hljs-string\">'services'<\/span>));\n});<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>This route builds a simple array of services (DB, Redis, Mailhog) from your <code>.env<\/code> and passes it to a Blade view.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\"><span class=\"hljs-comment\">&lt;!-- resources\/views\/sail\/status.blade.php --&gt;<\/span>\n@extends('layouts.app')\n@section('content')\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"container\"<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">h1<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"mb-4\"<\/span>&gt;<\/span>Sail Services<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">h1<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">ul<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"list-group\"<\/span>&gt;<\/span>\n    @foreach($services as $name =&gt; $url)\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">li<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"list-group-item\"<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">strong<\/span>&gt;<\/span>{{ $name }}:<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">strong<\/span>&gt;<\/span> <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">a<\/span> <span class=\"hljs-attr\">href<\/span>=<span class=\"hljs-string\">\"{{ $url }}\"<\/span> <span class=\"hljs-attr\">target<\/span>=<span class=\"hljs-string\">\"_blank\"<\/span>&gt;<\/span>{{ $url }}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">a<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">li<\/span>&gt;<\/span>\n    @endforeach\n  <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">ul<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n@endsection<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>The UI helps developers verify connections quickly. For real monitoring in production, use Horizon (#45) and Telescope (#48).<\/p>\n\n\n\n<div class=\"wp-block-spacer\" style=\"height:100px\" aria-hidden=\"true\"><\/div>\n\n\n\n<h2 class=\"wp-block-heading\">Wrapping Up<\/h2>\n\n\n\n<p>Laravel Sail is the fastest way to onboard developers with Docker: one command launches a full stack (PHP, DB, Redis, Mailhog). By customizing Dockerfiles, adding extensions, and using the Sail CLI, your team enjoys consistent environments without local setup headaches. For production, evolve the setup into lean images with proper Nginx, managed DB\/Redis, Horizon for queues, and CI\/CD pipelines. This hybrid approach keeps development simple but sets you up for scalable deployments.<\/p>\n\n\n\n<div class=\"wp-block-spacer\" style=\"height:100px\" aria-hidden=\"true\"><\/div>\n\n\n\n<h2 class=\"wp-block-heading\">What\u2019s Next<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"\/blog\/laravel-and-docker-setting-up-a-scalable-dev-environment\">Laravel and Docker: Setting Up a Scalable Dev Environment<\/a> \u2014 dive deeper into Docker for both dev &amp; production (Article #46).<\/li>\n<li><a href=\"\/blog\/cicd-for-laravel-projects-with-github-actions\">CI\/CD for Laravel Projects with GitHub Actions<\/a> \u2014 automate build &amp; deploy pipelines (Article #54).<\/li>\n<li><a href=\"\/blog\/deploying-laravel-on-aws-complete-guide-2025\">Deploying Laravel on AWS: Complete Guide (2025)<\/a> \u2014 containerize and run your app on ECS or EC2 (Article #52).<\/li>\n<\/ul>\n\n","protected":false},"excerpt":{"rendered":"<p>Laravel with Docker &amp; Sail: The Right Way Laravel Sail is the official Docker development environment for Laravel. It gives you a ready-to-use Docker Compose setup with PHP, MySQL\/Postgres, Redis, Mailhog, and more. In this article, we\u2019ll walk step-by-step through setting up Sail, customizing services, adding extensions, debugging containers, and preparing for production. This is [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":440,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[7],"tags":[74,77,70],"class_list":["post-436","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-laravel","tag-deployment","tag-dev-environment","tag-docker"],"_links":{"self":[{"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/posts\/436","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/comments?post=436"}],"version-history":[{"count":1,"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/posts\/436\/revisions"}],"predecessor-version":[{"id":439,"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/posts\/436\/revisions\/439"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/media\/440"}],"wp:attachment":[{"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/media?parent=436"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/categories?post=436"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/tags?post=436"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}