Zum Hauptinhalt springen
tsecurity.de LIVE
Echtzeit-Radar & Feeds
Alle RSS Feeds
👥 Community & Social
Web Security TippsIntroducing the new Confluence integration with Google Chat(22.09.2026 um 19:40 Uhr)
Web Security TippsQuick notes in Take notes for me(22.09.2026 um 21:31 Uhr)
Sichere ProgrammierungSecurity improvements for SSH(22.09.2026 um 16:11 Uhr)
Sichere ProgrammierungKI-Akzeptanz: Wie Rewe digital einfach nur den Chatbot umbenannte(22.09.2026 um 18:00 Uhr)
Sichere ProgrammierungClaude Opus 5.5: Keeping safety ahead of capabilities(22.09.2026 um 20:59 Uhr)
Sichere ProgrammierungYour Terraform Monolith Isn't Too Big. It's Tightly Coupled.(22.09.2026 um 21:00 Uhr)
Sichere ProgrammierungMy PR got merged into Mike — OSS Legal AI Platform 🎉(22.09.2026 um 21:34 Uhr)
Sichere ProgrammierungStop Writing JavaScript To Fix `100vh` On Mobile(22.09.2026 um 21:35 Uhr)
Sichere ProgrammierungNext.js proxy.ts Explained (with Cheat Sheet)(22.09.2026 um 21:36 Uhr)
Web Security TippsIntroducing the new Confluence integration with Google Chat(22.09.2026 um 19:40 Uhr)
Web Security TippsQuick notes in Take notes for me(22.09.2026 um 21:31 Uhr)
Sichere ProgrammierungSecurity improvements for SSH(22.09.2026 um 16:11 Uhr)
Sichere ProgrammierungKI-Akzeptanz: Wie Rewe digital einfach nur den Chatbot umbenannte(22.09.2026 um 18:00 Uhr)
Sichere ProgrammierungClaude Opus 5.5: Keeping safety ahead of capabilities(22.09.2026 um 20:59 Uhr)
Sichere ProgrammierungYour Terraform Monolith Isn't Too Big. It's Tightly Coupled.(22.09.2026 um 21:00 Uhr)
Sichere ProgrammierungMy PR got merged into Mike — OSS Legal AI Platform 🎉(22.09.2026 um 21:34 Uhr)
Sichere ProgrammierungStop Writing JavaScript To Fix `100vh` On Mobile(22.09.2026 um 21:35 Uhr)
Sichere ProgrammierungNext.js proxy.ts Explained (with Cheat Sheet)(22.09.2026 um 21:36 Uhr)
Intelligence View
⚡ tsecurity.de Intelligence

The Laravel 12 Docker Blueprint I Wish I Had: Nginx + PHP-FPM, Small Images, Clean CI/CD, and DigitalOcean Private Registry

This is the setup that turned my Laravel 12 project from “works on my machine” into “ready anytime”. The goal was simple: smaller builds, cleaner releases, and deployments that don’t need babysitting backed by CI/CD and a DigitalOcean priva…

0
↗ Quelle (dev.to)
Reagiere als Erste:r — dein Feedback zählt!

This is the setup that turned my Laravel 12 project from “works on my machine” into “ready anytime”. The goal was simple: smaller builds, cleaner releases, and deployments that don’t need babysitting backed by CI/CD and a DigitalOcean private registry.






What you’ll learn



In this post, I’ll walk through the blueprint I used to:




  • Run Laravel 12 on PHP 8.2 in containers (the minimum supported version).

  • Serve the app with Nginx + PHP-FPM (clean separation of concerns).

  • Build smaller images and keep releases tidy (so shipping feels repeatable).

  • Push/pull images using a DigitalOcean private container registry (DOCR).

  • Automate it with clean CI/CD so deploys don’t need babysitting.



First we will create this Dockerfile.




# Multi-stage build
FROM composer:2.8 AS composer-stage

FROM php:8.3-fpm-alpine

# Install dependencies
RUN apk add --no-cache \
nginx \
supervisor \
libpng-dev \
libjpeg-turbo-dev \
freetype-dev \
oniguruma-dev \
libxml2-dev \
libzip-dev \
zip \
unzip \
&& docker-php-ext-configure gd --with-freetype --with-jpeg \
&& docker-php-ext-install -j$(nproc) pdo_mysql mbstring exif pcntl bcmath gd opcache zip

# Copy Composer
COPY --from=composer-stage /usr/bin/composer /usr/bin/composer

WORKDIR /var/www/html

# Copy composer files
COPY composer.json composer.lock ./

# Install production dependencies WITHOUT running scripts
RUN composer install \
--no-interaction \
--no-dev \
--prefer-dist \
--no-scripts \
--no-autoloader

# Copy application
COPY . .

# Finish composer setup
RUN composer dump-autoload --optimize --classmap-authoritative --no-scripts

# Set permissions
RUN mkdir -p storage/logs storage/framework/{cache,sessions,views} bootstrap/cache \
&& touch database/database.sqlite \
&& chown -R www-data:www-data storage bootstrap/cache database/database.sqlite \
&& chmod -R 775 storage bootstrap/cache

# Nginx config
RUN echo 'server {' > /etc/nginx/http.d/default.conf && \
echo ' listen 3000;' >> /etc/nginx/http.d/default.conf && \
echo ' root /var/www/html/public;' >> /etc/nginx/http.d/default.conf && \
echo ' index index.php;' >> /etc/nginx/http.d/default.conf && \
echo ' location / {' >> /etc/nginx/http.d/default.conf && \
echo ' try_files $uri $uri/ /index.php?$query_string;' >> /etc/nginx/http.d/default.conf && \
echo ' }' >> /etc/nginx/http.d/default.conf && \
echo ' location ~ \.php$ {' >> /etc/nginx/http.d/default.conf && \
echo ' fastcgi_pass 127.0.0.1:9000;' >> /etc/nginx/http.d/default.conf && \
echo ' fastcgi_index index.php;' >> /etc/nginx/http.d/default.conf && \
echo ' fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;' >> /etc/nginx/http.d/default.conf && \
echo ' include fastcgi_params;' >> /etc/nginx/http.d/default.conf && \
echo ' }' >> /etc/nginx/http.d/default.conf && \
echo '}' >> /etc/nginx/http.d/default.conf

# Supervisor config
RUN echo '[supervisord]' > /etc/supervisord.conf && \
echo 'nodaemon=true' >> /etc/supervisord.conf && \
echo 'user=root' >> /etc/supervisord.conf && \
echo '[program:php-fpm]' >> /etc/supervisord.conf && \
echo 'command=php-fpm -F' >> /etc/supervisord.conf && \
echo 'autostart=true' >> /etc/supervisord.conf && \
echo 'autorestart=true' >> /etc/supervisord.conf && \
echo 'stdout_logfile=/dev/stdout' >> /etc/supervisord.conf && \
echo 'stdout_logfile_maxbytes=0' >> /etc/supervisord.conf && \
echo 'stderr_logfile=/dev/stderr' >> /etc/supervisord.conf && \
echo 'stderr_logfile_maxbytes=0' >> /etc/supervisord.conf && \
echo '[program:nginx]' >> /etc/supervisord.conf && \
echo 'command=nginx -g "daemon off;"' >> /etc/supervisord.conf && \
echo 'autostart=true' >> /etc/supervisord.conf && \
echo 'autorestart=true' >> /etc/supervisord.conf && \
echo 'stdout_logfile=/dev/stdout' >> /etc/supervisord.conf && \
echo 'stdout_logfile_maxbytes=0' >> /etc/supervisord.conf && \
echo 'stderr_logfile=/dev/stderr' >> /etc/supervisord.conf && \
echo 'stderr_logfile_maxbytes=0' >> /etc/supervisord.conf

# Entrypoint script
RUN echo '#!/bin/sh' > /entrypoint.sh && \
echo 'set -e' >> /entrypoint.sh && \
echo 'if [ ! -f .env ]; then' >> /entrypoint.sh && \
echo ' cp .env.example .env' >> /entrypoint.sh && \
echo 'fi' >> /entrypoint.sh && \
echo 'php artisan key:generate --force || true' >> /entrypoint.sh && \
echo 'php artisan config:cache || true' >> /entrypoint.sh && \
echo 'php artisan route:cache || true' >> /entrypoint.sh && \
echo 'php artisan view:cache || true' >> /entrypoint.sh && \
echo 'exec /usr/bin/supervisord -c /etc/supervisord.conf' >> /entrypoint.sh && \
chmod +x /entrypoint.sh

EXPOSE 3000

ENTRYPOINT ["/entrypoint.sh"]






Our Dockerfile is a multi stage Laravel image: it pulls in Composer from a lightweight builder stage, then runs on php-fpm (Alpine), installs Nginx + Supervisor + the PHP extensions Laravel commonly needs, installs production Composer deps (cached via composer.lock), copies the app, optimizes autoload, fixes Laravel storage/cache permissions, generates a basic Nginx vhost that serves /public and forwards PHP to FPM, and uses Supervisor to keep Nginx + PHP-FPM running together, Finally, the entrypoint ensures .env exists, warms up caches, then boots Supervisor and exposes port 3000.



Next, we will try to build it locally




docker build -t registry.digitalocean.com/hawkinstech/urecheapstore:latest .









docker tag registry.digitalocean.com/hawkinstech/laravel-app:latest registry.digitalocean.com/hawkinstech/urecheapstore:latest






Tag naming rules are strict (allowed characters, max length, etc.). DigitalOcean registry images follow this pattern: registry.digitalocean.com/<registry>/<image>:<tag>.



For local test, let's run our container.




docker run -d -p 3000:3000 --name urecheapstore registry.digitalocean.com/hawkinstech/urecheapstore:latest






After it run perfectly on local, let's setup CI/CD




name: Build and Push to DigitalOcean Registry

on:
push:
branches:
- master

env:
REGISTRY: registry.digitalocean.com/hawkinstech
IMAGE_NAME: urecheapstore

jobs:
build-and-push:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Install doctl
uses: digitalocean/action-doctl@v2
with:
token: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }}

- name: Log in to DigitalOcean Container Registry
run: doctl registry login --expiry-seconds 600

- name: Build and push Docker image
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
cache-to: type=inline

- name: Verify image push
run: |
echo "Image pushed successfully:"
echo "${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest"
echo "${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}"






This workflow turns every push to master into a fresh container release: it checks out your code, boots Docker Buildx (so builds are faster and cache friendly), installs doctl using your DIGITALOCEAN_ACCESS_TOKEN, then logs in to DigitalOcean Container Registry (DOCR) with short lived credentials. After that, it builds the image and pushes two tags—latest for “most recent deploy” and ${{ github.sha }} for an immutable, traceable version while reusing the previously pushed latest layer cache to speed up future builds.



Save this as .github/workflows/build-push.yml. On every push to master, it builds the Docker image and pushes it to registry.digitalocean.com/hawkinstech/urecheapstore with both latest and the commit SHA tag.






Wrap-up



At this point, you’ve got a repeatable flow: build the same Laravel image every time, ship it to DigitalOcean Container Registry, and let CI/CD do the boring work for you, so releases become predictable instead of stressful. The only “manual” part left is setting up the credentials once, and then you can forget about it and just push code.



If you want the missing setup steps, I split them into two short follow-ups:





  • Article 1: how to add DIGITALOCEAN_ACCESS_TOKEN as a GitHub Actions secret (Repo → Settings → Secrets and variables → Actions → New repository secret). (GitHub Docs)


  • Article 2: how to generate a DigitalOcean Personal Access Token (Control Panel → API → Personal access tokensGenerate New Token). (DigitalOcean Docs)



Bonus tip: the doctl registry login --expiry-seconds 600 approach is nice because it uses short-lived registry credentials during the workflow run. (DigitalOcean Docs)

Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten The Laravel 12 Docker Blueprint I Wish I Had: Nginx + PHP-FPM, Small Images, Clean CI/CD, and DigitalOcean Private Registry

Thematisch verwandte Begriffe: Laravel, Docker, Blueprint, Wish · 6 Treffer

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Zum Aktualisieren ziehen
ZERO-DAY CVE-2026-77259 | MCP Atlassian is a Model Context Protocol (MCP) server for Atlassian pro…
Advisory →
TTS Reader • tsecurity.de Voice
tsecurity.de Icon
tsecurity.de App
Offline-Lesen, Eilmeldungen & 0ms Ladezeit

Installiere tsecurity.de direkt auf deinen Home-Bildschirm für das ultimative Vollbild-Magazinerlebnis ohne Browser-Leisten.

Nächster Beitrag
Themen-Radar & Intelligence Matrix
Echtzeit-Taxonomie nach Angriffsvektoren & Plattformen

tsecurity.de Live Threat Radar

🔴 LIVE RADAR
MONITORING
AKTIV
CVE-DATENBANK
LIVE
🔍
Community Radar & Live Chat
Sentinel Bot online • Live-Stream
Dein Cluster: Security Explorer
Match:
lädt…
Verbindung zum Community-Stream wird aufgebaut...
Bearbeitungsmodus — Senden überschreibt deine Nachricht
Community-Puls — was gerade passiert
lädt…
Aktivitäten deiner Analysten
lädt…
Neues Thema oder Eilmeldung einreichen

Reiche interessante Links, Zero-Days oder Debatten ein. Die Community entscheidet per Upvote über die Veröffentlichung.

Heiß diskutierte Einreichungen
🔖 Gespeicherte Artikel
📂 Keine gespeicherten Artikel vorhanden.
Zurück Ziehen Vor
Links: vorheriger Artikel Rechts: nächster Artikel unten: schließen
News NIS-2 Frühwarnung Tier-1 Intel ⏱️ 3 Min vor 10 Min
Artikeldaten werden geladen...

Zurück: vorheriger Vor: nächster
↗ Original-Quelle
Social Reaktionen Deine Reaktion zählt
Einstufung & Relevanz-Poll 0 Stimmen
In sozialen Netzwerken teilen 1-Klick