{"id":426,"date":"2025-08-27T21:20:43","date_gmt":"2025-08-27T21:20:43","guid":{"rendered":"https:\/\/1v0.net\/blog\/?p=426"},"modified":"2025-08-27T21:20:45","modified_gmt":"2025-08-27T21:20:45","slug":"how-to-deploy-a-laravel-12-app-on-digitalocean","status":"publish","type":"post","link":"https:\/\/1v0.net\/blog\/how-to-deploy-a-laravel-12-app-on-digitalocean\/","title":{"rendered":"How to Deploy a Laravel 12 App on DigitalOcean"},"content":{"rendered":"\n<h2 class=\"wp-block-heading\"><strong>How to Deploy a Laravel 12 App on DigitalOcean<\/strong><\/h2>\n\n\n\n<p>DigitalOcean makes it fast to get a production-ready Laravel app online using Droplets (VMs), Managed Databases, and Spaces (S3-compatible storage). In this step-by-step guide, you\u2019ll provision a Droplet, install Nginx + PHP-FPM, configure env and services, secure the server with UFW + Let\u2019s Encrypt, wire Redis queues with Horizon, add a health check endpoint for your monitors, and ship with a zero-downtime deployment script.<\/p>\n\n\n\n<div class=\"wp-block-spacer\" style=\"height:100px\" aria-hidden=\"true\"><\/div>\n\n\n\n\n<h2 class=\"wp-block-heading\"><strong>1 &#8211; Create Your Droplet &amp; Prerequisites<\/strong><\/h2>\n\n\n\n<p>In the DigitalOcean dashboard, create an Ubuntu LTS Droplet (2 vCPU \/ 4GB RAM is a comfortable baseline), add your SSH key, and attach a floating IP if you want stable addressing. Optionally create a Managed MySQL\/PostgreSQL database and a Managed Redis (or run Redis locally to start). Point your domain\u2019s A record at the Droplet IP.<\/p>\n\n\n\n\n<div class=\"wp-block-spacer\" style=\"height:100px\" aria-hidden=\"true\"><\/div>\n\n\n\n\n<h2 class=\"wp-block-heading\"><strong>2 &#8211; Install Nginx, PHP-FPM, and Extensions<\/strong><\/h2>\n\n\n\n<p>SSH into the Droplet and install the web stack. We\u2019ll use PHP 8.3 for best performance and features.<\/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\"># on Ubuntu<\/span>\nsudo apt update\nsudo apt install -y nginx software-properties-common\nsudo add-apt-repository ppa:ondrej\/php -y\nsudo apt update\nsudo apt install -y php8.3-fpm php8.3-cli php8.3-mysql php8.3-xml php8.3-mbstring php8.3-curl php8.3-zip php8.3-bcmath unzip git\nsudo systemctl <span class=\"hljs-built_in\">enable<\/span> --now nginx php8.3-fpm<\/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>This installs Nginx and PHP-FPM with common Laravel extensions and enables both services on boot so a restart won\u2019t bring the app down.<\/p>\n\n\n\n\n<div class=\"wp-block-spacer\" style=\"height:100px\" aria-hidden=\"true\"><\/div>\n\n\n\n\n<h2 class=\"wp-block-heading\"><strong>3 &#8211; Create the Nginx Server Block<\/strong><\/h2>\n\n\n\n<p>Point Nginx to Laravel\u2019s <code>public<\/code> folder and pass PHP requests to PHP-FPM\u2019s Unix socket.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"Nginx\" data-shcb-language-slug=\"nginx\"><span><code class=\"hljs language-nginx\"><span class=\"hljs-comment\"># \/etc\/nginx\/sites-available\/laravel.conf<\/span>\n<span class=\"hljs-section\">server<\/span> {\n    <span class=\"hljs-attribute\">listen<\/span> <span class=\"hljs-number\">80<\/span>;\n    <span class=\"hljs-attribute\">server_name<\/span> your-domain.com www.your-domain.com;\n    <span class=\"hljs-attribute\">root<\/span> \/var\/www\/current\/public;\n\n    <span class=\"hljs-attribute\">index<\/span> index.php index.html;\n\n    <span class=\"hljs-attribute\">location<\/span> \/ {\n        <span class=\"hljs-attribute\">try_files<\/span> <span class=\"hljs-variable\">$uri<\/span> <span class=\"hljs-variable\">$uri<\/span>\/ \/index.php?<span class=\"hljs-variable\">$query_string<\/span>;\n    }\n\n    <span class=\"hljs-attribute\">location<\/span> <span class=\"hljs-regexp\">~ \\.php$<\/span> {\n        <span class=\"hljs-attribute\">include<\/span> snippets\/fastcgi-php.conf;\n        <span class=\"hljs-attribute\">fastcgi_pass<\/span> unix:\/run\/php\/php8.3-fpm.sock;\n        <span class=\"hljs-attribute\">fastcgi_read_timeout<\/span> <span class=\"hljs-number\">60s<\/span>;\n    }\n\n    <span class=\"hljs-attribute\">location<\/span> <span class=\"hljs-regexp\">~* \\.(?:css|js|jpg|jpeg|png|gif|webp|ico|woff2?)$<\/span> {\n        <span class=\"hljs-attribute\">expires<\/span> <span class=\"hljs-number\">7d<\/span>;\n        <span class=\"hljs-attribute\">access_log<\/span> <span class=\"hljs-literal\">off<\/span>;\n    }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Nginx<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">nginx<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>The <code>root<\/code> uses a <em>current<\/em> symlink, which we\u2019ll switch atomically during deploys. Static assets get 7-day caching; PHP routes go to <code>index.php<\/code>.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"Bash\" data-shcb-language-slug=\"bash\"><span><code class=\"hljs language-bash\">sudo ln -s \/etc\/nginx\/sites-available\/laravel.conf \/etc\/nginx\/sites-enabled\/\nsudo nginx -t &amp;&amp; sudo systemctl reload nginx<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><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 enables the site, lints the config, and reloads Nginx without downtime. For deeper tuning and hardening, see <a href=\"\/blog\/laravel-nginx-best-practices-for-production\">Laravel &amp; Nginx: Best Practices for Production<\/a> (Article #56).<\/p>\n\n\n\n\n<div class=\"wp-block-spacer\" style=\"height:100px\" aria-hidden=\"true\"><\/div>\n\n\n\n\n<h2 class=\"wp-block-heading\"><strong>4 &#8211; Clone App &amp; Prepare Directory Layout<\/strong><\/h2>\n\n\n\n<p>We\u2019ll use a releases pattern under <code>\/var\/www<\/code> for zero-downtime.<\/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\">sudo mkdir -p \/var\/www\/releases \/var\/www\/shared\nsudo chown -R <span class=\"hljs-variable\">$USER<\/span>:www-data \/var\/www\n<span class=\"hljs-built_in\">cd<\/span> \/var\/www\/releases\n<span class=\"hljs-comment\"># first release<\/span>\ngit <span class=\"hljs-built_in\">clone<\/span> https:\/\/github.com\/you\/your-laravel-app.git 20250828-000001\n<span class=\"hljs-built_in\">cd<\/span> 20250828-000001\ncomposer install --no-dev --optimize-autoloader\ncp .env.example \/var\/www\/shared\/.env\nphp artisan key:generate --force --env=production --no-interaction<\/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>The <code>shared<\/code> directory holds persistent files like <code>.env<\/code>, storage, etc. Each release gets its own folder; a <code>current<\/code> symlink will point to the active one.<\/p>\n\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\"># link shared storage into the release<\/span>\nrm -rf storage\nln -s \/var\/www\/shared\/storage storage\n<span class=\"hljs-comment\"># make current point to the release<\/span>\nln -sfn \/var\/www\/releases\/20250828-000001 \/var\/www\/current\nsudo chown -R www-data:www-data \/var\/www\/shared \/var\/www\/current\/storage\nphp artisan storage:link\nphp artisan config:cache &amp;&amp; php artisan route:cache &amp;&amp; php artisan view:cache<\/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>We symlink <code>storage<\/code> so logs\/uploads persist across deploys. Caching config\/routes\/views speeds up boot (see Article #41: <em>10 Proven Ways to Optimize Laravel for High Traffic<\/em>).<\/p>\n\n\n\n\n<div class=\"wp-block-spacer\" style=\"height:100px\" aria-hidden=\"true\"><\/div>\n\n\n\n\n<h2 class=\"wp-block-heading\"><strong>5 &#8211; Configure .env (Managed DB, Redis, Mail)<\/strong><\/h2>\n\n\n\n<p>If you created a Managed DB\/Redis in DigitalOcean, paste the connection strings into <code>\/var\/www\/shared\/.env<\/code>. Otherwise, point to your Droplet\u2019s local services.<\/p>\n\n\n<!-- DomainException(0): Unknown language: \"dotenv\" --># \/var\/www\/shared\/.env (example)\nAPP_ENV=production\nAPP_DEBUG=false\nAPP_URL=https:\/\/your-domain.com\n\nDB_CONNECTION=mysql\nDB_HOST=your-managed-mysql-do-user-1234.c.db.ondigitalocean.com\nDB_PORT=25060\nDB_DATABASE=app\nDB_USERNAME=doadmin\nDB_PASSWORD=super-secret\nMYSQL_ATTR_SSL_CA=\/etc\/ssl\/certs\/ca-certificates.crt\n\nCACHE_DRIVER=redis\nSESSION_DRIVER=redis\nREDIS_HOST=your-redis-do-1234.db.ondigitalocean.com\nREDIS_PASSWORD=redis-secret\nREDIS_PORT=25061\n\nQUEUE_CONNECTION=redis\nMAIL_MAILER=log\n\n\n<p>Managed DB\/Redis often require SSL and nonstandard ports. Keep <code>APP_DEBUG=false<\/code> to avoid exposing stack traces in production.<\/p>\n\n\n\n\n<div class=\"wp-block-spacer\" style=\"height:100px\" aria-hidden=\"true\"><\/div>\n\n\n\n\n<h2 class=\"wp-block-heading\"><strong>6 &#8211; Optional: Store Files on DigitalOcean Spaces<\/strong><\/h2>\n\n\n\n<p>Spaces is S3-compatible, so the standard S3 driver works. Great for offloading user uploads and serving via CDN.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\">\/\/ config\/filesystems.php (snippet)<\/span>\n<span class=\"hljs-string\">'disks'<\/span> =&gt; &#91;\n  <span class=\"hljs-string\">'spaces'<\/span> =&gt; &#91;\n    <span class=\"hljs-string\">'driver'<\/span> =&gt; <span class=\"hljs-string\">'s3'<\/span>,\n    <span class=\"hljs-string\">'key'<\/span>    =&gt; env(<span class=\"hljs-string\">'SPACES_KEY'<\/span>),\n    <span class=\"hljs-string\">'secret'<\/span> =&gt; env(<span class=\"hljs-string\">'SPACES_SECRET'<\/span>),\n    <span class=\"hljs-string\">'endpoint'<\/span> =&gt; env(<span class=\"hljs-string\">'SPACES_ENDPOINT'<\/span>, <span class=\"hljs-string\">'https:\/\/nyc3.digitaloceanspaces.com'<\/span>),\n    <span class=\"hljs-string\">'region'<\/span> =&gt; env(<span class=\"hljs-string\">'SPACES_REGION'<\/span>, <span class=\"hljs-string\">'nyc3'<\/span>),\n    <span class=\"hljs-string\">'bucket'<\/span> =&gt; env(<span class=\"hljs-string\">'SPACES_BUCKET'<\/span>),\n    <span class=\"hljs-string\">'url'<\/span>    =&gt; env(<span class=\"hljs-string\">'SPACES_CDN_URL'<\/span>), <span class=\"hljs-comment\">\/\/ e.g., CDN endpoint<\/span>\n    <span class=\"hljs-string\">'visibility'<\/span> =&gt; <span class=\"hljs-string\">'public'<\/span>,\n  ],\n]<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><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>Set <code>FILESYSTEM_DISK=spaces<\/code> and configure <code>SPACES_* <\/code> vars. The optional <code>SPACES_CDN_URL<\/code> makes <code>Storage::url()<\/code> return CDN-hosted URLs automatically.<\/p>\n\n\n\n\n<div class=\"wp-block-spacer\" style=\"height:100px\" aria-hidden=\"true\"><\/div>\n\n\n\n\n<h2 class=\"wp-block-heading\"><strong>7 &#8211; Queues with Horizon (Redis)<\/strong><\/h2>\n\n\n\n<p>Use Horizon for live metrics, auto-balancing, and easier on-call. See Article #45 for the full dashboard walkthrough.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"Bash\" data-shcb-language-slug=\"bash\"><span><code class=\"hljs language-bash\">composer require laravel\/horizon\nphp artisan horizon:install\nphp artisan migrate<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><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 adds Horizon\u2019s config and tables. Horizon exposes a dashboard at <code>\/horizon<\/code> for job throughput, failures, and retries.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"TOML, also INI\" data-shcb-language-slug=\"ini\"><span><code class=\"hljs language-ini\"><span class=\"hljs-comment\"># \/etc\/systemd\/system\/horizon.service<\/span>\n<span class=\"hljs-section\">&#91;Unit]<\/span>\n<span class=\"hljs-attr\">Description<\/span>=Laravel Horizon\n<span class=\"hljs-attr\">After<\/span>=network.target\n<span class=\"hljs-section\">&#91;Service]<\/span>\n<span class=\"hljs-attr\">User<\/span>=www-data\n<span class=\"hljs-attr\">WorkingDirectory<\/span>=\/var\/www\/current\n<span class=\"hljs-attr\">ExecStart<\/span>=\/usr\/bin\/php artisan horizon\n<span class=\"hljs-attr\">Restart<\/span>=always\n<span class=\"hljs-attr\">RestartSec<\/span>=<span class=\"hljs-number\">3<\/span>\n<span class=\"hljs-section\">&#91;Install]<\/span>\n<span class=\"hljs-attr\">WantedBy<\/span>=multi-user.target<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">TOML, also INI<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">ini<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Running Horizon under systemd keeps it alive across crashes and deploys. For queue fundamentals, check Article #42: <em>How to Use Laravel Queues for Faster Performance<\/em>.<\/p>\n\n\n\n\n<div class=\"wp-block-spacer\" style=\"height:100px\" aria-hidden=\"true\"><\/div>\n\n\n\n\n<h2 class=\"wp-block-heading\"><strong>8 &#8211; Free SSL with Let\u2019s Encrypt<\/strong><\/h2>\n\n\n\n<p>Use Certbot\u2019s Nginx plugin to get and auto-renew TLS certificates.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"Bash\" data-shcb-language-slug=\"bash\"><span><code class=\"hljs language-bash\">sudo apt install -y certbot python3-certbot-nginx\nsudo certbot --nginx -d your-domain.com -d www.your-domain.com\n<span class=\"hljs-comment\"># renewals are installed to crontab\/systemd timers automatically<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><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>Certbot edits your Nginx config to redirect HTTP\u2192HTTPS and configures auto-renewal, keeping your TLS valid without manual steps.<\/p>\n\n\n\n\n<div class=\"wp-block-spacer\" style=\"height:100px\" aria-hidden=\"true\"><\/div>\n\n\n\n\n<h2 class=\"wp-block-heading\"><strong>9 &#8211; Basic Firewall (UFW)<\/strong><\/h2>\n\n\n\n<p>Lock down the Droplet so only SSH and web ports are exposed.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"Bash\" data-shcb-language-slug=\"bash\"><span><code class=\"hljs language-bash\">sudo ufw allow OpenSSH\nsudo ufw allow <span class=\"hljs-string\">\"Nginx Full\"<\/span>\nsudo ufw --force <span class=\"hljs-built_in\">enable<\/span>\nsudo ufw status<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><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 opens ports 22, 80, and 443 while blocking everything else by default. Pair with SSH keys (no password auth) for secure access.<\/p>\n\n\n\n\n<div class=\"wp-block-spacer\" style=\"height:100px\" aria-hidden=\"true\"><\/div>\n\n\n\n\n<h2 class=\"wp-block-heading\"><strong>10 &#8211; Health Check Endpoint (UI)<\/strong><\/h2>\n\n\n\n<p>Add a lightweight endpoint for monitors (or a load balancer later) to validate DB\/Redis connectivity quickly.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-11\" 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\">'\/health'<\/span>, <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-params\">()<\/span> <\/span>{\n    <span class=\"hljs-keyword\">try<\/span> {\n        DB::connection()-&gt;getPdo();\n        Cache::put(<span class=\"hljs-string\">'healthcheck'<\/span>, now(), <span class=\"hljs-number\">5<\/span>);\n        <span class=\"hljs-keyword\">return<\/span> response()-&gt;json(&#91;<span class=\"hljs-string\">'status'<\/span> =&gt; <span class=\"hljs-string\">'ok'<\/span>, <span class=\"hljs-string\">'time'<\/span> =&gt; now()], <span class=\"hljs-number\">200<\/span>);\n    } <span class=\"hljs-keyword\">catch<\/span> (\\Throwable $e) {\n        <span class=\"hljs-keyword\">return<\/span> response()-&gt;json(&#91;<span class=\"hljs-string\">'status'<\/span> =&gt; <span class=\"hljs-string\">'fail'<\/span>], <span class=\"hljs-number\">500<\/span>);\n    }\n});<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-11\"><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>The route touches both DB and cache. If either fails, your monitoring will catch it. Keep the logic minimal so checks are fast and reliable.<\/p>\n\n\n\n\n<div class=\"wp-block-spacer\" style=\"height:100px\" aria-hidden=\"true\"><\/div>\n\n\n\n\n<h2 class=\"wp-block-heading\"><strong>11 &#8211; Zero-Downtime Deployment Script<\/strong><\/h2>\n\n\n\n<p>Use an atomic symlink swap after warming the release (composer\/artisan\/migrations). This pattern keeps users online during updates.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-12\" data-shcb-language-name=\"Bash\" data-shcb-language-slug=\"bash\"><span><code class=\"hljs language-bash\"><span class=\"hljs-comment\"># \/usr\/local\/bin\/deploy.sh<\/span>\n<span class=\"hljs-built_in\">set<\/span> -euo pipefail\nAPP=\/var\/www\nREL=<span class=\"hljs-variable\">$APP<\/span>\/releases\nNEW=<span class=\"hljs-variable\">$REL<\/span>\/$(date +%Y%m%d%H%M%S)\n\nmkdir -p <span class=\"hljs-string\">\"<span class=\"hljs-variable\">$NEW<\/span>\"<\/span>\n<span class=\"hljs-comment\"># Upload or fetch the build (choose one)<\/span>\n<span class=\"hljs-comment\"># rsync -az --delete .\/build\/ \"$NEW\/\"<\/span>\ngit <span class=\"hljs-built_in\">clone<\/span> --depth=1 https:\/\/github.com\/you\/your-laravel-app.git <span class=\"hljs-string\">\"<span class=\"hljs-variable\">$NEW<\/span>\"<\/span>\n\n<span class=\"hljs-built_in\">cd<\/span> <span class=\"hljs-string\">\"<span class=\"hljs-variable\">$NEW<\/span>\"<\/span>\nln -sfn \/var\/www\/shared\/.env .env\nrm -rf storage &amp;&amp; ln -s \/var\/www\/shared\/storage storage\n\ncomposer install --no-dev --optimize-autoloader\nphp artisan config:cache\nphp artisan route:cache\nphp artisan view:cache\nphp artisan migrate --force\n\nln -sfn <span class=\"hljs-string\">\"<span class=\"hljs-variable\">$NEW<\/span>\"<\/span> <span class=\"hljs-string\">\"<span class=\"hljs-variable\">$APP<\/span>\/current\"<\/span>\nsudo systemctl reload php8.3-fpm\nsudo systemctl reload nginx\nsudo systemctl restart horizon || <span class=\"hljs-literal\">true<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-12\"><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>The script clones or syncs your build into a timestamped folder, warms caches, applies migrations, then flips <code>current<\/code>. Finally it reloads PHP-FPM\/Nginx and restarts Horizon.<\/p>\n\n\n\n<p>Automate this from GitHub Actions for push-button deploys\u2014see <a href=\"\/blog\/cicd-for-laravel-projects-with-github-actions\">CI\/CD for Laravel Projects with GitHub Actions<\/a> (Article #54).<\/p>\n\n\n\n\n<div class=\"wp-block-spacer\" style=\"height:100px\" aria-hidden=\"true\"><\/div>\n\n\n\n\n<h2 class=\"wp-block-heading\">Wrapping Up<\/h2>\n\n\n\n<p>You now have a secure, optimized Laravel deployment on DigitalOcean: Nginx + PHP-FPM on a Droplet, Managed DB\/Redis, Spaces for files, HTTPS via Let\u2019s Encrypt, UFW firewall, Horizon for queues, a health endpoint, and zero-downtime releases. As traffic grows, you can scale vertically (bigger Droplet) or horizontally (multiple Droplets behind a load balancer), and complement with the performance strategies from Article #41.<\/p>\n\n\n\n\n<div class=\"wp-block-spacer\" style=\"height:100px\" aria-hidden=\"true\"><\/div>\n\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\/cicd-for-laravel-projects-with-github-actions\">CI\/CD for Laravel Projects with GitHub Actions<\/a> \u2014 automate builds, tests, and zero-downtime deploys.<\/li>\n<li><a href=\"\/blog\/laravel-nginx-best-practices-for-production\">Laravel &amp; Nginx: Best Practices for Production<\/a> \u2014 tune timeouts, compression, and buffers.<\/li>\n<li><a href=\"\/blog\/laravel-deployment-checklist-for-2025\">Laravel Deployment Checklist for 2025<\/a> \u2014 run this pre-launch audit before every release.<\/li>\n<li><a href=\"\/blog\/optimizing-laravel-for-high-concurrency-with-octane\">Optimizing Laravel for High Concurrency with Octane<\/a> \u2014 supercharge RPS when you need it.<\/li>\n<\/ul>\n\n","protected":false},"excerpt":{"rendered":"<p>How to Deploy a Laravel 12 App on DigitalOcean DigitalOcean makes it fast to get a production-ready Laravel app online using Droplets (VMs), Managed Databases, and Spaces (S3-compatible storage). In this step-by-step guide, you\u2019ll provision a Droplet, install Nginx + PHP-FPM, configure env and services, secure the server with UFW + Let\u2019s Encrypt, wire Redis [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":430,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[7],"tags":[74,71],"class_list":["post-426","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-laravel","tag-deployment","tag-devops"],"_links":{"self":[{"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/posts\/426","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=426"}],"version-history":[{"count":1,"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/posts\/426\/revisions"}],"predecessor-version":[{"id":429,"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/posts\/426\/revisions\/429"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/media\/430"}],"wp:attachment":[{"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/media?parent=426"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/categories?post=426"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/tags?post=426"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}