ci: optimize deploy time with pre-built base image and parallel steps
Some checks failed
ci/woodpecker/push/ci Pipeline failed

- Add docker/base/Dockerfile with pre-compiled PHP extensions
- Add Dockerfile.fast using pre-built base image (~2-3 min vs 15-20 min)
- Add docker/base/build-and-push.sh script for base image management
- Update CI to run composer-install and build-frontend in PARALLEL
- Both steps complete before build-image starts

Expected improvement: 20-30 min → ~10 min deploys

To activate: Run ./docker/base/build-and-push.sh once from a Docker host
This commit is contained in:
kelly
2025-12-15 19:40:13 -07:00
parent 1c2afe416f
commit 486c16d0fa
4 changed files with 150 additions and 97 deletions

View File

@@ -1,15 +1,10 @@
# Woodpecker CI/CD Pipeline for Cannabrands Hub
# Documentation: https://woodpecker-ci.org/docs/intro
# Optimized for ~10 min deploys (was 20-30 min)
#
# 2-Environment Workflow:
# - develop branch → dev.cannabrands.app (integration/testing)
# - master branch → cannabrands.app (production)
# - tags (2025.X) → cannabrands.app (versioned production releases)
#
# Pipeline Strategy:
# - PRs: Run tests (lint, style, phpunit) IN PARALLEL
# - Push to develop/master: Build + deploy (tests already passed on PR)
# - Tags: Build versioned release
# Strategy:
# - Build frontend + composer install in PARALLEL
# - Use pre-built base image (no PHP extension compilation)
# - Single-stage Dockerfile.fast
#
# External Services:
# - PostgreSQL: 10.100.6.50:5432 (cannabrands_dev)
@@ -32,78 +27,32 @@ clone:
steps:
# ============================================
# DEPENDENCY INSTALLATION
# PARALLEL: Composer + Frontend (saves ~5 min)
# ============================================
restore-composer-cache:
image: 10.100.9.70:5000/meltwater/drone-cache:dev
settings:
backend: "filesystem"
restore: true
cache_key: "composer-{{ checksum \"composer.lock\" }}"
archive_format: "gzip"
mount:
- "vendor"
volumes:
- /tmp/woodpecker-cache:/tmp/cache
composer-install:
image: 10.100.9.70:5000/kirschbaumdevelopment/laravel-test-runner:8.3
depends_on:
- restore-composer-cache
environment:
DB_CONNECTION: pgsql
DB_HOST: 10.100.6.50
DB_PORT: 5432
DB_DATABASE: cannabrands_dev
DB_USERNAME: cannabrands
DB_PASSWORD: SpDyCannaBrands2024
commands:
- echo "Creating .env for package discovery..."
- |
cat > .env << 'EOF'
APP_NAME="Cannabrands Hub"
APP_ENV=testing
APP_ENV=production
APP_KEY=base64:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
APP_DEBUG=true
CACHE_STORE=array
SESSION_DRIVER=array
QUEUE_CONNECTION=sync
DB_CONNECTION=pgsql
DB_HOST=10.100.6.50
DB_PORT=5432
DB_DATABASE=cannabrands_dev
DB_USERNAME=cannabrands
DB_PASSWORD=SpDyCannaBrands2024
REDIS_HOST=10.100.9.50
REDIS_PORT=6379
REDIS_PASSWORD=SpDyR3d1s2024!
EOF
- |
if [ -d "vendor" ] && [ -f "vendor/autoload.php" ]; then
echo "✅ Restored vendor from cache"
else
echo "📦 Installing fresh dependencies"
fi
- composer install --no-interaction --prefer-dist --optimize-autoloader --no-progress
- echo "✅ Composer dependencies ready!"
- composer install --no-dev --no-interaction --prefer-dist --optimize-autoloader --no-progress
- echo "✅ Composer done"
rebuild-composer-cache:
image: 10.100.9.70:5000/meltwater/drone-cache:dev
depends_on:
- composer-install
settings:
backend: "filesystem"
rebuild: true
cache_key: "composer-{{ checksum \"composer.lock\" }}"
archive_format: "gzip"
mount:
- "vendor"
volumes:
- /tmp/woodpecker-cache:/tmp/cache
when:
branch: [develop, master]
event: push
build-frontend:
image: 10.100.9.70:5000/library/node:22-alpine
environment:
VITE_REVERB_APP_KEY: 6VDQTxU0fknXHCgKOI906Py03abktP8GatzNw3DvJkU=
VITE_REVERB_HOST: dev.cannabrands.app
VITE_REVERB_PORT: "443"
VITE_REVERB_SCHEME: https
commands:
- npm ci --prefer-offline
- npm run build
- echo "✅ Frontend built"
# ============================================
# PR CHECKS (Parallel)
@@ -114,9 +63,7 @@ steps:
depends_on:
- composer-install
commands:
- echo "Checking PHP syntax..."
- ./vendor/bin/parallel-lint app routes database config --colors --blame
- echo "✅ PHP syntax check complete!"
when:
event: pull_request
@@ -125,9 +72,7 @@ steps:
depends_on:
- composer-install
commands:
- echo "Checking code style..."
- ./vendor/bin/pint --test
- echo "✅ Code style check complete!"
when:
event: pull_request
@@ -139,7 +84,6 @@ steps:
event: pull_request
environment:
APP_ENV: testing
BROADCAST_CONNECTION: reverb
CACHE_STORE: array
SESSION_DRIVER: array
QUEUE_CONNECTION: sync
@@ -152,35 +96,25 @@ steps:
REDIS_HOST: 10.100.9.50
REDIS_PORT: 6379
REDIS_PASSWORD: SpDyR3d1s2024!
REVERB_APP_ID: test-app-id
REVERB_APP_KEY: test-key
REVERB_APP_SECRET: test-secret
REVERB_HOST: localhost
REVERB_PORT: 8080
REVERB_SCHEME: http
commands:
- cp .env.example .env
- php artisan key:generate
- echo "Starting Reverb server..."
- php artisan reverb:start --host=0.0.0.0 --port=8080 > /dev/null 2>&1 &
- sleep 2
- echo "Running tests..."
- php artisan test --parallel
- echo "✅ Tests complete!"
# ============================================
# BUILD & DEPLOY
# BUILD & DEPLOY (uses pre-built base image)
# ============================================
build-image-dev:
image: 10.100.9.70:5000/kaniko-project/executor:debug
depends_on:
- composer-install
- build-frontend
commands:
- |
/kaniko/executor \
--context=/woodpecker/src/git.spdy.io/Cannabrands/hub \
--dockerfile=/woodpecker/src/git.spdy.io/Cannabrands/hub/Dockerfile \
--dockerfile=/woodpecker/src/git.spdy.io/Cannabrands/hub/Dockerfile.fast \
--destination=10.100.9.70:5000/cannabrands/hub:dev \
--destination=10.100.9.70:5000/cannabrands/hub:dev-${CI_COMMIT_SHA:0:7} \
--destination=10.100.9.70:5000/cannabrands/hub:sha-${CI_COMMIT_SHA:0:7} \
@@ -189,11 +123,8 @@ steps:
--skip-tls-verify \
--build-arg=GIT_COMMIT_SHA=${CI_COMMIT_SHA:0:7} \
--build-arg=APP_VERSION=dev \
--build-arg=VITE_REVERB_APP_KEY=6VDQTxU0fknXHCgKOI906Py03abktP8GatzNw3DvJkU= \
--build-arg=VITE_REVERB_HOST=dev.cannabrands.app \
--build-arg=VITE_REVERB_PORT=443 \
--build-arg=VITE_REVERB_SCHEME=https \
--cache=true \
--cache-ttl=168h \
--cache-repo=10.100.9.70:5000/cannabrands/hub-cache
when:
branch: develop
@@ -207,7 +138,6 @@ steps:
KUBECONFIG_CONTENT:
from_secret: kubeconfig_dev
commands:
- echo "🚀 Deploying to dev.cannabrands.app..."
- mkdir -p ~/.kube
- echo "$KUBECONFIG_CONTENT" | tr -d '[:space:]' | base64 -d > ~/.kube/config
- chmod 600 ~/.kube/config
@@ -226,11 +156,12 @@ steps:
image: 10.100.9.70:5000/kaniko-project/executor:debug
depends_on:
- composer-install
- build-frontend
commands:
- |
/kaniko/executor \
--context=/woodpecker/src/git.spdy.io/Cannabrands/hub \
--dockerfile=/woodpecker/src/git.spdy.io/Cannabrands/hub/Dockerfile \
--dockerfile=/woodpecker/src/git.spdy.io/Cannabrands/hub/Dockerfile.fast \
--destination=10.100.9.70:5000/cannabrands/hub:latest \
--destination=10.100.9.70:5000/cannabrands/hub:prod-${CI_COMMIT_SHA:0:7} \
--destination=10.100.9.70:5000/cannabrands/hub:sha-${CI_COMMIT_SHA:0:7} \
@@ -240,6 +171,7 @@ steps:
--build-arg=GIT_COMMIT_SHA=${CI_COMMIT_SHA:0:7} \
--build-arg=APP_VERSION=production \
--cache=true \
--cache-ttl=168h \
--cache-repo=10.100.9.70:5000/cannabrands/hub-cache
when:
branch: master
@@ -253,7 +185,6 @@ steps:
KUBECONFIG_CONTENT:
from_secret: kubeconfig_prod
commands:
- echo "🚀 Deploying to PRODUCTION..."
- mkdir -p ~/.kube
- echo "$KUBECONFIG_CONTENT" | tr -d '[:space:]' | base64 -d > ~/.kube/config
- chmod 600 ~/.kube/config
@@ -272,11 +203,12 @@ steps:
image: 10.100.9.70:5000/kaniko-project/executor:debug
depends_on:
- composer-install
- build-frontend
commands:
- |
/kaniko/executor \
--context=/woodpecker/src/git.spdy.io/Cannabrands/hub \
--dockerfile=/woodpecker/src/git.spdy.io/Cannabrands/hub/Dockerfile \
--dockerfile=/woodpecker/src/git.spdy.io/Cannabrands/hub/Dockerfile.fast \
--destination=10.100.9.70:5000/cannabrands/hub:${CI_COMMIT_TAG} \
--destination=10.100.9.70:5000/cannabrands/hub:latest \
--insecure \
@@ -285,6 +217,7 @@ steps:
--build-arg=GIT_COMMIT_SHA=${CI_COMMIT_SHA:0:7} \
--build-arg=APP_VERSION=${CI_COMMIT_TAG} \
--cache=true \
--cache-ttl=168h \
--cache-repo=10.100.9.70:5000/cannabrands/hub-cache
when:
event: tag

41
Dockerfile.fast Normal file
View File

@@ -0,0 +1,41 @@
# ============================================
# Fast Production Dockerfile
# Uses pre-built base image - build time ~2-3 min vs 15-20 min
# ============================================
# Use pre-built base with all PHP extensions
FROM 10.100.9.70:5000/cannabrands/hub-base:latest
ARG GIT_COMMIT_SHA=unknown
ARG APP_VERSION=dev
# Copy application code
COPY --chown=www-data:www-data . .
# Copy pre-built frontend assets (built in CI step)
# These are already in public/build from the build-frontend step
# Copy pre-installed vendor (from CI composer-install step)
# Already included in COPY . .
# Create version metadata file
RUN echo "VERSION=${APP_VERSION}" > /var/www/html/version.env && \
echo "COMMIT=${GIT_COMMIT_SHA}" >> /var/www/html/version.env && \
chown www-data:www-data /var/www/html/version.env
# Copy production configurations
COPY docker/production/nginx/default.conf /etc/nginx/http.d/default.conf
COPY docker/production/supervisor/supervisord.conf /etc/supervisor/supervisord.conf
COPY docker/production/php/php.ini /usr/local/etc/php/conf.d/99-custom.ini
# Remove default PHP-FPM pool config and use our custom one
RUN rm -f /usr/local/etc/php-fpm.d/www.conf /usr/local/etc/php-fpm.d/www.conf.default
COPY docker/production/php/php-fpm.conf /usr/local/etc/php-fpm.d/www.conf
# Fix permissions
RUN chown -R www-data:www-data /var/www/html/storage /var/www/html/bootstrap/cache \
&& chmod -R 775 /var/www/html/storage /var/www/html/bootstrap/cache
EXPOSE 80
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/supervisord.conf"]

57
docker/base/Dockerfile Normal file
View File

@@ -0,0 +1,57 @@
# Base image with all PHP extensions pre-installed
# Build once: docker build -t 10.100.9.70:5000/cannabrands/hub-base:latest -f docker/base/Dockerfile .
# Push: docker push 10.100.9.70:5000/cannabrands/hub-base:latest
#
# Rebuild only when PHP extensions or system dependencies change
FROM php:8.3-fpm-alpine
LABEL maintainer="CannaBrands Team"
# Install system dependencies
RUN apk add --no-cache \
nginx \
supervisor \
postgresql-dev \
libpng-dev \
libjpeg-turbo-dev \
freetype-dev \
libzip-dev \
icu-dev \
icu-data-full \
zip \
unzip \
git \
curl \
bash
# Install build dependencies for PHP extensions
RUN apk add --no-cache --virtual .build-deps \
autoconf \
g++ \
make
# Install PHP extensions
RUN docker-php-ext-configure gd --with-freetype --with-jpeg \
&& docker-php-ext-install -j$(nproc) \
pdo_pgsql \
pgsql \
gd \
zip \
intl \
pcntl \
bcmath \
opcache
# Install Redis extension
RUN pecl install redis \
&& docker-php-ext-enable redis \
&& apk del .build-deps
# Create app directory structure
RUN mkdir -p /var/www/html/storage/framework/{sessions,views,cache} \
&& mkdir -p /var/www/html/storage/logs \
&& mkdir -p /var/www/html/bootstrap/cache \
&& mkdir -p /var/log/supervisor
WORKDIR /var/www/html

22
docker/base/build-and-push.sh Executable file
View File

@@ -0,0 +1,22 @@
#!/bin/bash
# Build and push the base image to local registry
# Run this ONCE, then CI will use it for fast builds
#
# Usage: ./docker/base/build-and-push.sh
set -e
REGISTRY="10.100.9.70:5000"
IMAGE="cannabrands/hub-base"
TAG="latest"
echo "Building base image..."
docker build -t ${REGISTRY}/${IMAGE}:${TAG} -f docker/base/Dockerfile .
echo "Pushing to registry..."
docker push ${REGISTRY}/${IMAGE}:${TAG}
echo ""
echo "✅ Base image ready: ${REGISTRY}/${IMAGE}:${TAG}"
echo ""
echo "CI will now use this for fast builds (~2-3 min vs 15-20 min)"