Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
node_modules
dist
.git
.github
.devcontainer
docs
CLAUDE.md
56 changes: 56 additions & 0 deletions .github/workflows/docker.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
name: Docker image

on:
push:
branches: [main]
tags: ['v*']
pull_request:
paths:
- Dockerfile
- docker/**
- .github/workflows/docker.yml
workflow_dispatch:

jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4

- uses: docker/setup-qemu-action@v3

- uses: docker/setup-buildx-action@v3

- name: Log in to GHCR
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Image metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository }}
tags: |
type=raw,value=latest,enable={{is_default_branch}}
type=semver,pattern={{version}}
type=semver,pattern={{major}}
type=sha

- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
# arm64 so the image runs on a Raspberry Pi in the pit
platforms: linux/amd64,linux/arm64
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
15 changes: 15 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
FROM node:22-alpine AS build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
# GitHub Pages serves from /QRScout/; containers serve from the domain root.
ARG BASE_PATH=/
RUN npm run build -- --base=$BASE_PATH

FROM nginx:1.27-alpine
COPY docker/nginx.conf /etc/nginx/conf.d/default.conf
COPY --from=build /app/dist /usr/share/nginx/html
EXPOSE 80
HEALTHCHECK --interval=30s --timeout=3s \
CMD wget -qO /dev/null http://127.0.0.1/healthz || exit 1
26 changes: 26 additions & 0 deletions docker/nginx.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
index index.html;

gzip on;
gzip_types text/css application/javascript application/json image/svg+xml;

location = /healthz {
access_log off;
return 200 "ok";
}

# Hashed build assets are safe to cache forever.
location /assets/ {
add_header Cache-Control "public, max-age=31536000, immutable";
}

# Teams mount their scouting config at
# /usr/share/nginx/html/team-config.json and load it in the app
# via https://<host>/team-config.json.
location / {
try_files $uri $uri/ /index.html;
}
}
8 changes: 4 additions & 4 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,20 @@
<meta name="keywords" content="Keywords" />
<title>QR Scout</title>

<link rel="manifest" href="/QRScout/manifest.json" />
<link rel="manifest" href="%BASE_URL%manifest.json" />
<link
href="/QRScout/icons/favicon-16x16.png"
href="%BASE_URL%icons/favicon-16x16.png"
rel="icon"
type="image/png"
sizes="16x16"
/>
<link
href="/QRScout/icons/favicon-32x32.png"
href="%BASE_URL%icons/favicon-32x32.png"
rel="icon"
type="image/png"
sizes="32x32"
/>
<link rel="apple-touch-icon" href="/QRScout/apple-icon.png" />
<link rel="apple-touch-icon" href="%BASE_URL%apple-icon.png" />
<meta name="theme-color" content="#317EFB" />
</head>
<body>
Expand Down
4 changes: 2 additions & 2 deletions public/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
"background_color": "#f03d41",
"display": "standalone",
"orientation": "portrait",
"scope": "/QRScout/",
"start_url": "/QRScout/",
"scope": "./",
"start_url": "./",
"icons": [
{"src": "icons/icon-36x36.png", "sizes": "36x36", "type": "image/png"},
{"src": "icons/icon-48x48.png", "sizes": "48x48", "type": "image/png"},
Expand Down
Loading