Fix nginx serving blank page by baking client into its own image

The runtime cp from etsy-tracker to a shared volume silently failed
because the nodejs user lacked write permissions on the root-owned volume.
Now a dedicated nginx-frontend Docker stage is built and pushed to GHCR
with the React client baked in, eliminating the volume-sharing approach.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
dlawler489 2026-04-21 21:29:07 +10:00
parent 89ee6e69fc
commit 1ef51b1e37
3 changed files with 33 additions and 25 deletions

View file

@ -72,7 +72,7 @@ jobs:
exit 1 exit 1
fi fi
- name: Build and push Docker image - name: Build and push API image
uses: docker/build-push-action@v5 uses: docker/build-push-action@v5
with: with:
context: . context: .
@ -83,6 +83,30 @@ jobs:
cache-from: type=gha cache-from: type=gha
cache-to: type=gha,mode=max cache-to: type=gha,mode=max
- name: Extract metadata for Nginx image
id: nginx-meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-nginx
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha,prefix=sha-
- name: Build and push Nginx image
uses: docker/build-push-action@v5
with:
context: .
target: nginx-frontend
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.nginx-meta.outputs.tags }}
labels: ${{ steps.nginx-meta.outputs.labels }}
platforms: linux/amd64,linux/arm64
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Make package public - name: Make package public
if: github.event_name != 'pull_request' if: github.event_name != 'pull_request'
run: | run: |

View file

@ -62,4 +62,9 @@ HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
# Start the API server # Start the API server
ENTRYPOINT ["dumb-init", "--"] ENTRYPOINT ["dumb-init", "--"]
CMD ["node", "server/index.js"] CMD ["node", "server/index.js"]
# Stage 4: Nginx server with pre-built client
FROM nginx:alpine AS nginx-frontend
COPY --from=client-build /app/client/dist /usr/share/nginx/html
COPY nginx.deploy.conf /etc/nginx/nginx.conf

View file

@ -1,13 +1,10 @@
services: services:
# Nginx reverse proxy and static file server # Nginx reverse proxy and static file server (client baked into image)
nginx: nginx:
image: nginx:alpine image: ghcr.io/dlawler489/etsy-finance-tracker-nginx:main
container_name: etsy-nginx container_name: etsy-nginx
ports: ports:
- "3000:80" - "3000:80"
volumes:
- ./nginx.deploy.conf:/etc/nginx/nginx.conf:ro
- client_dist:/usr/share/nginx/html:ro
depends_on: depends_on:
- etsy-tracker - etsy-tracker
restart: unless-stopped restart: unless-stopped
@ -31,12 +28,8 @@ services:
- PORT=8080 - PORT=8080
- CLIENT_URL=http://nginx - CLIENT_URL=http://nginx
volumes: volumes:
# Mount data directory for persistent storage
- etsy_data:/app/data - etsy_data:/app/data
# Optional: Mount uploads directory if needed
- etsy_uploads:/app/uploads - etsy_uploads:/app/uploads
# Share client build with nginx
- client_dist:/usr/share/nginx/html
restart: unless-stopped restart: unless-stopped
networks: networks:
- etsy-network - etsy-network
@ -46,24 +39,10 @@ services:
timeout: 10s timeout: 10s
retries: 3 retries: 3
start_period: 40s start_period: 40s
# Copy client files to shared volume and start server
command: >
sh -c "
echo 'Starting Etsy Finance Tracker API Server...';
if [ ! -f /usr/share/nginx/html/index.html ]; then
echo 'Copying client files to shared volume...';
cp -r /app/client/dist/* /usr/share/nginx/html/ 2>/dev/null || true;
echo 'Client files copied successfully';
fi;
echo 'Starting Node.js server...';
exec node server/dist/index.js
"
volumes: volumes:
etsy_uploads: etsy_uploads:
driver: local driver: local
client_dist:
driver: local
etsy_data: etsy_data:
driver: local driver: local