fairycat

Created:
Updated:

Laravel Dockerfile

Dockerfile

# syntax=docker/dockerfile:1

FROM php:8.2-fpm AS fpm-base

RUN echo "Types: deb\n\
# http://snapshot.debian.org/archive/debian/20250203T000000Z\n\
URIs: https://mirrors.cloud.tencent.com/debian\n\
Suites: bookworm bookworm-updates\n\
Components: main\n\
Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg\n\
\n\
Types: deb\n\
# http://snapshot.debian.org/archive/debian-security/20250203T000000Z\n\
URIs: https://mirrors.cloud.tencent.com/debian-security\n\
Suites: bookworm-security\n\
Components: main\n\
Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg\n" > /etc/apt/sources.list.d/debian.sources

# see https://github.com/mlocati/docker-php-extension-installer
RUN --mount=type=cache,id=/bookworm/var/cache/apt,target=/var/cache/apt,sharing=locked \
    --mount=type=bind,from=mlocati/php-extension-installer:latest,source=/usr/bin/install-php-extensions,target=/usr/local/bin/install-php-extensions \
    install-php-extensions @composer bcmath gd mongodb pcntl sockets zip pdo_mysql opcache \
    && apt-get update && apt-get install -y cron vim iputils-ping default-mysql-client sqlite3 net-tools && rm -rf /var/lib/apt/lists/*

FROM fpm-base AS fpm-base-dev
RUN mv "$PHP_INI_DIR/php.ini-development" "$PHP_INI_DIR/php.ini" && \
    rm "$PHP_INI_DIR/conf.d/docker-php-ext-opcache.ini"

FROM fpm-base AS fpm-base-prod
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"

FROM fpm-base-dev AS deps-dev
# 安装 composer 依赖 dev
WORKDIR /var/www/laravel
RUN --mount=type=bind,source=./composer.json,target=composer.json \
    --mount=type=bind,source=./composer.lock,target=composer.lock \
    --mount=type=cache,target=/root/.composer/cache,sharing=locked \
    composer install --no-scripts --no-interaction

FROM fpm-base-prod AS deps-prod
# 安装 composer 依赖 no-dev
WORKDIR /var/www/laravel
RUN --mount=type=bind,source=./composer.json,target=composer.json \
    --mount=type=bind,source=./composer.lock,target=composer.lock \
    --mount=type=cache,target=/root/.composer/cache,sharing=locked \
    composer install --no-scripts --no-interaction --no-dev

FROM fpm-base-dev AS fpm-dev-plate
# 开发中 plate,不安装 composer 依赖
WORKDIR /var/www/laravel
RUN echo "* * * * * root su www-data -s /bin/bash -c '/usr/local/bin/php /var/www/laravel/artisan schedule:run' > /proc/1/fd/1 2>/proc/1/fd/2" > /etc/cron.d/laravel \
    && chmod 644 /etc/cron.d/laravel

FROM fpm-base-dev AS fpm-dev
# 包含 dev 的镜像
WORKDIR /var/www/laravel
RUN echo "* * * * * root su www-data -s /bin/bash -c '/usr/local/bin/php /var/www/laravel/artisan schedule:run' > /proc/1/fd/1 2>/proc/1/fd/2" > /etc/cron.d/laravel \
    && chmod 644 /etc/cron.d/laravel
COPY --from=deps-dev /var/www/laravel/vendor/ /var/www/laravel/vendor
COPY . /var/www/laravel/
RUN chown www-data:www-data \
    bootstrap/cache \
    storage \
    storage/app \
    storage/app/public \
    storage/framework \
    storage/framework/cache \
    storage/framework/cache/data \
    storage/framework/sessions \
    storage/framework/testing \
    storage/framework/views \
    storage/logs \
    && chmod 3777 \
    bootstrap/cache \
    storage \
    storage/app \
    storage/app/public \
    storage/framework \
    storage/framework/cache \
    storage/framework/cache/data \
    storage/framework/sessions \
    storage/framework/testing \
    storage/framework/views \
    storage/logs \
    && chmod +x artisan

RUN composer dumpautoload --no-interaction

FROM fpm-base-prod AS fpm
# 正式部署的镜像
WORKDIR /var/www/laravel
RUN echo "* * * * * root su www-data -s /bin/bash -c '/usr/local/bin/php /var/www/laravel/artisan schedule:run' > /proc/1/fd/1 2>/proc/1/fd/2" > /etc/cron.d/laravel \
    && chmod 644 /etc/cron.d/laravel
COPY --from=deps-prod /var/www/laravel/vendor/ /var/www/laravel/vendor
COPY . /var/www/laravel/
RUN chown www-data:www-data \
    bootstrap/cache \
    storage \
    storage/app \
    storage/app/public \
    storage/framework \
    storage/framework/cache \
    storage/framework/cache/data \
    storage/framework/sessions \
    storage/framework/testing \
    storage/framework/views \
    storage/logs \
    && chmod 3777 \
    bootstrap/cache \
    storage \
    storage/app \
    storage/app/public \
    storage/framework \
    storage/framework/cache \
    storage/framework/cache/data \
    storage/framework/sessions \
    storage/framework/testing \
    storage/framework/views \
    storage/logs \
    && chmod +x artisan

RUN composer dumpautoload --no-interaction --no-dev -o --apcu

FROM nginx:latest AS nginx
# 静态资源镜像
WORKDIR /var/www/laravel
COPY ./public/ /var/www/laravel/public

Jenkinsfile

pipeline {
    agent any
    environment {
        docker_registry = 'https://docker-registry.demo.cn/'
        docker_credentials_id = 'docker-registry-login'
    }
    stages {
        stage("Initialization") {
            steps {
                script {
                    // use name of the patchset as the build name
                    buildName "#${env.BUILD_NUMBER} ${tag}"
                    buildDescription "${description}"
                }
            }
        }
        stage('Docker Build Base') {
            steps {
                script {
                    sh "docker build --target fpm-base ."
                    sh "docker build --target deps-dev ."
                    sh "docker build --target deps-prod ."
                }
            }
        }
        stage('Docker Build') {
            parallel {
                stage('Docker Build Fpm Dev') {
                    steps {
                        script {
                            docker.withRegistry(docker_registry, docker_credentials_id) {
                                // tag 可能是标签也可能是分支名,获取 ‘/’ 后的部分作为版本名称
                                def version = "${tag}".split("/")[-1]
                                version = "${version}-dev"
                                def imageId = "fpm:${version}"
                                sh "docker build --target fpm-dev -t ${imageId} ."
                                def image = docker.image(imageId)
                                image.push(version)
                            }
                        }
                    }
                }
                stage('Docker Build Fpm') {
                    steps {
                        script {
                            docker.withRegistry(docker_registry, docker_credentials_id) {
                                // tag 可能是标签也可能是分支名,获取 ‘/’ 后的部分作为版本名称
                                def version = "${tag}".split("/")[-1]
                                def imageId = "fpm:${version}"
                                sh "docker build --target fpm -t ${imageId} ."
                                def image = docker.image(imageId)
                                image.push(version)
                            }
                        }
                    }
                }
                stage('Docker Build Nginx') {
                    steps {
                        script {
                            docker.withRegistry(docker_registry, docker_credentials_id) {
                                // tag 可能是标签也可能是分支名,获取 ‘/’ 后的部分作为版本名称
                                def version = "${tag}".split("/")[-1]
                                def imageId = "my-nginx:${version}"
                                sh "docker build --target nginx -t ${imageId} ."
                                def image = docker.image(imageId)
                                image.push(version)
                            }
                        }

                    }
                }
            }
        }
    }
}

Jenkins

参数化构建过程:

  • git 参数 tag 默认 ${tag}
  • 字符串参数 description

webhook trigger 参数:

  • tag 默认 refs/heads/master
  • description 默认 ${tag}

流水线:pipeline script from SCM

  • Branches to build 指定分支:${tag}

docker compose

services:
  fpm:
    image: fpm:${MY_VERSION:-latest}
    restart: unless-stopped
    post_start:
      - command: sh -c "php artisan config:cache && php artisan route:cache && php artisan event:cache"
    networks:
      - fpm
    volumes:
      - laravel-storage:/var/www/laravel/storage
      - laravel-storage-app:/var/www/laravel/storage/app
      - laravel-storage-logs:/var/www/laravel/storage/logs

  schedule:
    extends: fpm
    command: [ 'cron', '-f' ]
  horizon:
    extends: fpm
    command: [ 'php', 'artisan', 'horizon' ]
    user: www-data:www-data
  reverb:
    extends: fpm
    command: [ 'php', 'artisan', 'reverb:start' ]
    user: www-data:www-data

  nginx:
    image: my-nginx:${MY_VERSION:-latest}
    restart: unless-stopped
    networks:
      - nginx
      - fpm
    environment:
      - NGINX_PORT=80
    volumes:
      - ./nginx.conf.templates:/etc/nginx/templates
      - ./nginx.conf.d:/etc/nginx/conf.d
      - ./nginx.log:/var/log/nginx
      - laravel-storage-app:/var/www/laravel/storage/app

networks:
  fpm:
  nginx:

volumes:
  laravel-storage:
  laravel-storage-app:
  laravel-storage-logs:

nginx conf

server {
    listen       80;
    listen  [::]:80;

    access_log  /var/log/nginx/host.access.log  main;

    add_header X-Frame-Options "SAMEORIGIN";
    add_header X-XSS-Protection "1; mode=block";
    add_header X-Content-Type-Options "nosniff";

    index index.html index.htm index.php;

    charset utf-8;

    absolute_redirect off;

    root /var/www/laravel/public;
    location / {
        # 以 / 结尾的路径但不是目录,通过跳转去掉
        location ~ ^.*/$ {
            if (!-d $request_filename) {
                rewrite ^(.*)/$ $1 permanent;
            }
        }
        # 非静态资源,重写到转发 fpm
        #try_files $uri $uri/ /index.php?$query_string;
        try_files $uri $uri/ @fpm;
    }

    # storage 路径下全部为静态资源
    location /storage/ {
        alias /var/www/laravel/storage/app/public/;
        expires 12h;
        # 静态资源 php 禁止转发 fpm,全部视为文本
        location ~ \.php$ {
            types {
                text/plain php;
            }
        }
    }

    # vue 根目录,路径重写到入口
    #location /vue/ {
    #    try_files $uri $uri/ /vue/index.html;
    #}

    location = /favicon.ico { access_log off; log_not_found off; }
    location = /robots.txt  { access_log off; log_not_found off; }

    # php 转发 fpm
    location ~ \.php$ {
        fastcgi_pass  fpm:9000;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        include fastcgi_params;
    }

    location ~ /\.(?!well-known).* {
        deny all;
    }

    @ php 转发 fpm
    location @fpm {
        fastcgi_pass  fpm:9000;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $realpath_root/index.php;
        include fastcgi_params;
    }
}

评论

Name

Email

Website

Subject