Skip to content

Commit ca2a1a2

Browse files
committed
Add fat base image for faster Docker builds
- Create Dockerfile.base with all shared dependencies (Node, Go, PHP, Playwright) - Update all child Dockerfiles to use ghcr.io/freegle/freegle-base:latest - Add GitHub Actions workflow for monthly base image rebuilds - Fix CircleCI PHP coverage upload (use composer.phar, writable json_path) - Add test artifacts to .dockerignore to reduce context transfer This reduces clean build time from ~11min to ~6min by: - Eliminating redundant package installation across containers - Reducing Docker context transfer by ~290MB - Removing duplicate apt-get operations
1 parent a8f1a72 commit ca2a1a2

File tree

9 files changed

+156
-45
lines changed

9 files changed

+156
-45
lines changed

.circleci/orb/freegle-tests.yml

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ commands:
7676
ls -la secrets/
7777
7878
# Start services in detached mode (use base file only, not override)
79+
# Note: base image is defined in docker-compose.yml and builds automatically
7980
docker-compose -f docker-compose.yml up -d
8081
8182
echo "Waiting for basic services to start..."
@@ -689,17 +690,21 @@ commands:
689690
echo "DEBUG: Coverage file head:"
690691
head -20 /tmp/phpunit-clover.xml || true
691692
692-
# Install php-coveralls in the container and run from there
693+
# Install php-coveralls in the container using composer.phar
693694
echo "DEBUG: Installing php-coveralls in container..."
694-
docker exec freegle-apiv1 bash -c "cd /var/www/iznik && composer require php-coveralls/php-coveralls --dev 2>&1" || echo "DEBUG: composer require failed"
695+
docker exec freegle-apiv1 bash -c "cd /var/www/iznik && php composer.phar require php-coveralls/php-coveralls --dev 2>&1" || echo "DEBUG: composer require failed"
695696
696697
# Copy coverage file to container
697698
echo "DEBUG: Copying coverage file to container..."
698699
docker cp /tmp/phpunit-clover.xml freegle-apiv1:/var/www/iznik/clover.xml
699700
700-
# Create coveralls config in container
701+
# Create coveralls config in container with writable json_path
701702
echo "DEBUG: Creating .coveralls.yml in container..."
702-
docker exec freegle-apiv1 bash -c "echo 'coverage_clover: clover.xml' > /var/www/iznik/.coveralls.yml && echo 'service_name: circleci' >> /var/www/iznik/.coveralls.yml"
703+
docker exec freegle-apiv1 bash -c "cat > /var/www/iznik/.coveralls.yml << 'COVERALLS_EOF'
704+
coverage_clover: clover.xml
705+
json_path: /tmp/coveralls-upload.json
706+
service_name: circleci
707+
COVERALLS_EOF"
703708

704709
# Run php-coveralls from within the container (which has PHP)
705710
echo "DEBUG: Running php-coveralls in container..."
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
name: Build Base Image
2+
3+
on:
4+
push:
5+
paths:
6+
- 'Dockerfile.base'
7+
- 'scripts/retry.sh'
8+
branches:
9+
- master
10+
schedule:
11+
# Monthly rebuild on 1st of each month at 3am UTC
12+
- cron: '0 3 1 * *'
13+
workflow_dispatch:
14+
15+
jobs:
16+
build:
17+
runs-on: ubuntu-latest
18+
steps:
19+
- name: Checkout
20+
uses: actions/checkout@v4
21+
22+
- name: Set up Docker Buildx
23+
uses: docker/setup-buildx-action@v3
24+
25+
- name: Login to GitHub Container Registry
26+
uses: docker/login-action@v3
27+
with:
28+
registry: ghcr.io
29+
username: ${{ github.actor }}
30+
password: ${{ secrets.GITHUB_TOKEN }}
31+
32+
- name: Build and push
33+
uses: docker/build-push-action@v5
34+
with:
35+
context: .
36+
file: ./Dockerfile.base
37+
push: true
38+
tags: |
39+
ghcr.io/${{ github.repository_owner }}/freegle-base:latest
40+
ghcr.io/${{ github.repository_owner }}/freegle-base:${{ github.sha }}
41+
cache-from: type=gha
42+
cache-to: type=gha,mode=max

Dockerfile.base

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
# Freegle Fat Base Image
2+
# Contains all dependencies for Node, Go, PHP containers
3+
# Build: docker build -f Dockerfile.base -t freegle-base .
4+
# This image is built locally and used as the base for all Freegle containers
5+
6+
FROM ubuntu:22.04
7+
8+
ENV DEBIAN_FRONTEND=noninteractive \
9+
TZ='UTC'
10+
11+
# Copy retry script for flaky network operations
12+
COPY scripts/retry.sh /usr/local/bin/retry
13+
RUN chmod +x /usr/local/bin/retry
14+
15+
# ============ COMMON TOOLS ============
16+
RUN apt-get update && apt-get install -y \
17+
ca-certificates curl gnupg wget git vim \
18+
build-essential python3 \
19+
zip unzip jq netcat telnet \
20+
iputils-ping net-tools dnsutils \
21+
lsb-release openssl \
22+
&& rm -rf /var/lib/apt/lists/*
23+
24+
# ============ NODE.JS 22 ============
25+
RUN curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \
26+
&& apt-get install -y nodejs \
27+
&& npm config set fetch-retries 5 \
28+
&& npm config set fetch-retry-mintimeout 20000 \
29+
&& npm config set fetch-retry-maxtimeout 120000
30+
31+
# ============ GO 1.23 ============
32+
RUN curl -fsSL https://go.dev/dl/go1.23.0.linux-amd64.tar.gz | tar -C /usr/local -xzf -
33+
ENV PATH="/usr/local/go/bin:/root/go/bin:${PATH}" \
34+
GOPATH="/root/go" \
35+
GOPROXY="https://proxy.golang.org,direct"
36+
37+
# ============ PHP 8.1 + WEB SERVER ============
38+
# nginx: serves PHP API via php-fpm
39+
# postfix: mail relay for sending emails (configured to use mailhog)
40+
RUN apt-get update && apt-get install -y \
41+
php8.1-fpm php8.1-cli php8.1-mysql php8.1-pgsql php8.1-redis \
42+
php8.1-curl php8.1-zip php8.1-gd php8.1-mbstring php8.1-xml \
43+
php8.1-intl php8.1-xdebug php-mailparse php8.1-simplexml \
44+
php8.1-xmlrpc php8.1-pdo-mysql \
45+
libxml2-dev libzip-dev zlib1g-dev libcurl4-openssl-dev \
46+
libpng-dev libgmp-dev libjpeg-turbo8-dev libpq-dev \
47+
php-pear php-dev libgeoip-dev php-mbstring \
48+
nginx postfix cron rsyslog openssh-server \
49+
default-mysql-client postgresql-client \
50+
tesseract-ocr geoip-bin geoipupdate \
51+
&& rm -rf /var/lib/apt/lists/*
52+
53+
# ============ PLAYWRIGHT DEPS ============
54+
RUN apt-get update && apt-get install -y \
55+
xvfb dbus libglib2.0-0 libnss3 libnspr4 libatk1.0-0 \
56+
libatk-bridge2.0-0 libcups2 libxkbcommon0 libatspi2.0-0 \
57+
libxcomposite1 libxdamage1 libgbm1 libpango-1.0-0 \
58+
libcairo2 libasound2 \
59+
&& mkdir -p /var/run/dbus \
60+
&& rm -rf /var/lib/apt/lists/*
61+
62+
# ============ DOCKER CLI ============
63+
RUN install -m 0755 -d /etc/apt/keyrings \
64+
&& curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg \
65+
&& chmod a+r /etc/apt/keyrings/docker.gpg \
66+
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu jammy stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null \
67+
&& apt-get update && apt-get install -y docker-ce-cli docker-compose-plugin \
68+
&& rm -rf /var/lib/apt/lists/*
69+
70+
# ============ GOOGLE CLOUD SDK ============
71+
RUN curl -O https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google-cloud-cli-linux-x86_64.tar.gz \
72+
&& tar -xf google-cloud-cli-linux-x86_64.tar.gz \
73+
&& mv google-cloud-sdk /opt/ \
74+
&& /opt/google-cloud-sdk/install.sh --quiet --usage-reporting=false --path-update=false \
75+
&& rm google-cloud-cli-linux-x86_64.tar.gz
76+
ENV PATH="/opt/google-cloud-sdk/bin:${PATH}"
77+
78+
# ============ GO SWAGGER (for apiv2) ============
79+
RUN go install github.com/go-swagger/go-swagger/cmd/swagger@v0.31.0
80+
81+
# ============ PLAYWRIGHT BROWSERS ============
82+
RUN npx playwright install chromium && npx playwright install-deps
83+
84+
# Remove packages that conflict with our setup
85+
RUN apt-get update && apt-get remove -y apache2* sendmail* mlocate php-ssh2 || true && rm -rf /var/lib/apt/lists/*
86+
87+
# Final cleanup
88+
RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

docker-compose.yml

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,14 @@
11
services:
2+
# Base image - must be built first, all other containers depend on it
3+
base:
4+
image: freegle-base:latest
5+
build:
6+
context: .
7+
dockerfile: Dockerfile.base
8+
command: ["true"] # Exits immediately, just used for building
9+
profiles:
10+
- build # Only runs when explicitly requested or as dependency
11+
212
reverse-proxy:
313
container_name: freegle-traefik
414
networks:
@@ -220,8 +230,6 @@ services:
220230
build:
221231
context: ./status
222232
network: host
223-
additional_contexts:
224-
scripts: ./scripts
225233
container_name: freegle-status
226234
networks:
227235
- default
@@ -305,8 +313,6 @@ services:
305313
build:
306314
context: ./iznik-server
307315
network: host
308-
additional_contexts:
309-
scripts: ./scripts
310316
args:
311317
IZNIK_SERVER_BRANCH: ${IZNIK_SERVER_BRANCH:-master}
312318
depends_on:
@@ -385,8 +391,6 @@ services:
385391
build:
386392
context: ./iznik-server-go
387393
network: host
388-
additional_contexts:
389-
scripts: ./scripts
390394
args:
391395
IZNIK_SERVER_GO_BRANCH: ${IZNIK_SERVER_GO_BRANCH:-master}
392396
depends_on:
@@ -442,8 +446,6 @@ services:
442446
build:
443447
context: ./iznik-nuxt3
444448
network: host
445-
additional_contexts:
446-
scripts: ./scripts
447449
args:
448450
IZNIK_NUXT3_BRANCH: ${IZNIK_NUXT3_BRANCH:-master}
449451
tmpfs:
@@ -494,8 +496,6 @@ services:
494496
context: ./iznik-nuxt3
495497
dockerfile: Dockerfile.prod
496498
network: host
497-
additional_contexts:
498-
scripts: ./scripts
499499
args:
500500
IZNIK_NUXT3_BRANCH: ${IZNIK_NUXT3_BRANCH:-master}
501501
tmpfs:
@@ -544,10 +544,8 @@ services:
544544
- "apiv2.localhost:host-gateway"
545545
build:
546546
context: .
547-
dockerfile: ./iznik-nuxt3-modtools/Dockerfile
547+
dockerfile: ./iznik-nuxt3-modtools/modtools/Dockerfile
548548
network: host
549-
additional_contexts:
550-
scripts: ./scripts
551549
args:
552550
IZNIK_NUXT3_MODTOOLS_BRANCH: ${IZNIK_NUXT3_MODTOOLS_BRANCH:-master}
553551
tmpfs:
@@ -604,8 +602,6 @@ services:
604602
context: ./iznik-nuxt3-modtools
605603
dockerfile: modtools/Dockerfile.prod
606604
network: host
607-
additional_contexts:
608-
scripts: ./scripts
609605
args:
610606
IZNIK_NUXT3_MODTOOLS_BRANCH: ${IZNIK_NUXT3_MODTOOLS_BRANCH:-master}
611607
tmpfs:
@@ -655,8 +651,6 @@ services:
655651
context: ./iznik-nuxt3
656652
dockerfile: Dockerfile.playwright
657653
network: host
658-
additional_contexts:
659-
scripts: ./scripts
660654
working_dir: /app
661655
extra_hosts:
662656
- "freegle-dev.localhost:127.0.0.1"

iznik-server

iznik-server-go

status/Dockerfile

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,9 @@
1-
FROM node:18-slim
1+
FROM ghcr.io/freegle/freegle-base:latest
22

33
WORKDIR /app
44

5-
# Copy retry script for flaky network operations
6-
COPY --from=scripts retry.sh /usr/local/bin/retry
7-
RUN chmod +x /usr/local/bin/retry
8-
9-
# Install docker CLI from official Docker repository (with retry for flaky networks including DNS)
10-
RUN retry bash -c 'apt-get update && apt-get install -y ca-certificates curl gnupg' \
11-
&& install -m 0755 -d /etc/apt/keyrings \
12-
&& retry bash -c 'curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg' \
13-
&& chmod a+r /etc/apt/keyrings/docker.gpg \
14-
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian bookworm stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null \
15-
&& retry bash -c 'apt-get update && apt-get install -y docker-ce-cli' \
16-
&& rm -rf /var/lib/apt/lists/*
17-
185
COPY package.json ./
19-
20-
# Configure npm retries for flaky networks
21-
RUN npm config set fetch-retries 5 && \
22-
npm config set fetch-retry-mintimeout 20000 && \
23-
npm config set fetch-retry-maxtimeout 120000 && \
24-
npm install
6+
RUN npm install
257

268
COPY . .
279

0 commit comments

Comments
 (0)