From 0b5f1b1f19605bbeae90a14e3704cc3d8c23106e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 26 Nov 2025 05:17:29 +0000 Subject: [PATCH 1/8] Initial plan From b07a40f5fe92e12c2800f0ddfa4e4ab75d75a5af Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 26 Nov 2025 05:29:59 +0000 Subject: [PATCH 2/8] Add Docker configuration for frontend and backend services Co-authored-by: Rikul <1149512+Rikul@users.noreply.github.com> --- .dockerignore | 15 +++++++++++++++ Dockerfile | 30 ++++++++++++++++++++++++++++++ README.md | 36 +++++++++++++++++++++++++++++++++++- docker-compose.yml | 28 ++++++++++++++++++++++++++++ nginx.conf | 36 ++++++++++++++++++++++++++++++++++++ server/.dockerignore | 8 ++++++++ server/Dockerfile | 22 ++++++++++++++++++++++ server/db.js | 4 +++- 8 files changed, 177 insertions(+), 2 deletions(-) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 docker-compose.yml create mode 100644 nginx.conf create mode 100644 server/.dockerignore create mode 100644 server/Dockerfile diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..da7bb8e --- /dev/null +++ b/.dockerignore @@ -0,0 +1,15 @@ +node_modules +dist +dist-ssr +*.local +.git +.gitignore +*.md +.env +.env.local +.vscode +.idea +*.log +npm-debug.log* +bills.db +tmp diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..d28e460 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,30 @@ +# Frontend Dockerfile - Multi-stage build for lightweight image +FROM node:22-alpine AS builder + +WORKDIR /app + +# Copy package files +COPY package.json ./ + +# Install dependencies +RUN npm install + +# Copy source files +COPY . . + +# Build the application +RUN npm run build + +# Production stage - serve with lightweight nginx +FROM nginx:alpine + +# Copy built assets from builder stage +COPY --from=builder /app/dist /usr/share/nginx/html + +# Copy custom nginx configuration +COPY nginx.conf /etc/nginx/conf.d/default.conf + +# Expose port 80 +EXPOSE 80 + +CMD ["nginx", "-g", "daemon off;"] diff --git a/README.md b/README.md index 75f93f6..0be4b57 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,8 @@ BillScan consists of two parts: ## Prerequisites -- Node.js (v18+ recommended) +- Node.js (v18+ recommended) - for local development +- Docker and Docker Compose - for containerized deployment - API key for at least one AI service (see AI Service Configuration below) ## AI Service Configuration @@ -49,6 +50,32 @@ BillScan supports multiple AI services for bill/receipt extraction. Configure wh If `AI_SERVICE` is not set, the application defaults to `gemini` for backward compatibility. +## Quick Start with Docker + +The easiest way to run BillScan is with Docker Compose: + +1. Build and start the containers: + ```bash + docker compose up --build + ``` + +2. Access the application: + - Frontend: http://localhost:8080 + - Backend API: http://localhost:3000 + +3. To stop the containers: + ```bash + docker compose down + ``` + +**Note:** The SQLite database is persisted in a Docker volume (`billscan-data`), so your data will be preserved across container restarts. + +### Docker Architecture + +- **Frontend**: Multi-stage build using Node.js for building and nginx:alpine for serving (~30MB final image) +- **Backend**: Node.js Alpine image running Express server (~150MB) +- **Data Persistence**: SQLite database stored in a named Docker volume + ## Setup & Run (Frontend) From the project root: @@ -146,3 +173,10 @@ The application uses a pluggable AI service architecture: - If the server fails to start, ensure port 3000 is free. - Delete `server/bills.db` to reset stored data (schema recreated automatically). - Large image uploads: body size limit set to `50mb`; adjust in `server/index.js` if needed. + +### Docker Troubleshooting + +- If Docker build fails, ensure you have Docker and Docker Compose installed. +- To reset Docker data: `docker compose down -v` (removes volumes and data). +- View container logs: `docker compose logs -f` +- Rebuild containers after code changes: `docker compose up --build` diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..32966e6 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,28 @@ +services: + frontend: + build: + context: . + dockerfile: Dockerfile + ports: + - "8080:80" + depends_on: + - backend + environment: + - NODE_ENV=production + restart: unless-stopped + + backend: + build: + context: ./server + dockerfile: Dockerfile + ports: + - "3000:3000" + volumes: + - billscan-data:/data + environment: + - NODE_ENV=production + - DATA_DIR=/data + restart: unless-stopped + +volumes: + billscan-data: diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 0000000..340c045 --- /dev/null +++ b/nginx.conf @@ -0,0 +1,36 @@ +server { + listen 80; + server_name localhost; + root /usr/share/nginx/html; + index index.html; + + # Enable gzip compression + gzip on; + gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; + + # Handle SPA routing - redirect all requests to index.html + location / { + try_files $uri $uri/ /index.html; + } + + # Proxy API requests to backend + location /api { + proxy_pass http://backend:3000; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_cache_bypass $http_upgrade; + # Increase body size limit for image uploads + client_max_body_size 50M; + } + + # Cache static assets + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + } +} diff --git a/server/.dockerignore b/server/.dockerignore new file mode 100644 index 0000000..4245c32 --- /dev/null +++ b/server/.dockerignore @@ -0,0 +1,8 @@ +node_modules +*.log +npm-debug.log* +bills.db +.git +.gitignore +*.md +tmp diff --git a/server/Dockerfile b/server/Dockerfile new file mode 100644 index 0000000..33a1355 --- /dev/null +++ b/server/Dockerfile @@ -0,0 +1,22 @@ +# Backend Dockerfile - Lightweight Node.js image +FROM node:22-alpine + +WORKDIR /app + +# Copy package files +COPY package.json ./ + +# Install dependencies +RUN npm install --production + +# Copy source files +COPY . . + +# Create data directory for SQLite database persistence +RUN mkdir -p /data + +# Expose port 3000 +EXPOSE 3000 + +# Start the server +CMD ["node", "index.js"] diff --git a/server/db.js b/server/db.js index 4eab494..89b9c96 100644 --- a/server/db.js +++ b/server/db.js @@ -6,8 +6,10 @@ let dbInstance = null; async function getDb() { if (!dbInstance) { + // Use DATA_DIR environment variable if set, otherwise use current directory + const dataDir = process.env.DATA_DIR || __dirname; dbInstance = await open({ - filename: path.join(__dirname, 'bills.db'), + filename: path.join(dataDir, 'bills.db'), driver: sqlite3.Database }); From 365e59e371b2f8cbb8d17b822906465721a5a514 Mon Sep 17 00:00:00 2001 From: Rikul Patel Date: Wed, 26 Nov 2025 12:02:09 -0600 Subject: [PATCH 3/8] Update README.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0be4b57..baa5abd 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ The easiest way to run BillScan is with Docker Compose: 2. Access the application: - Frontend: http://localhost:8080 - - Backend API: http://localhost:3000 + - Backend API: http://localhost:8080/api (proxied through frontend) 3. To stop the containers: ```bash From 282fc5f9b4dc3e855db459e7146d0a5e291b9a88 Mon Sep 17 00:00:00 2001 From: Rikul Patel Date: Wed, 26 Nov 2025 15:36:38 -0600 Subject: [PATCH 4/8] Update nginx.conf Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- nginx.conf | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/nginx.conf b/nginx.conf index 340c045..ddb0223 100644 --- a/nginx.conf +++ b/nginx.conf @@ -4,6 +4,11 @@ server { root /usr/share/nginx/html; index index.html; + # Security headers + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-XSS-Protection "1; mode=block" always; + add_header Referrer-Policy "strict-origin-when-cross-origin" always; # Enable gzip compression gzip on; gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; From 2cf6e74746284aeb53be4f3b780e577a792d4a5e Mon Sep 17 00:00:00 2001 From: Rikul Patel Date: Wed, 26 Nov 2025 15:37:16 -0600 Subject: [PATCH 5/8] Update Dockerfile Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- Dockerfile | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/Dockerfile b/Dockerfile index d28e460..d8153cc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,6 +9,25 @@ COPY package.json ./ # Install dependencies RUN npm install +# Accept build arguments for API keys +ARG GEMINI_API_KEY +ARG OPENAI_API_KEY +ARG ANTHROPIC_API_KEY +ARG OLLAMA_HOST +ARG OLLAMA_MODEL +ARG OPENAI_MODEL +ARG ANTHROPIC_MODEL +ARG AI_SERVICE + +# Set as environment variables for the build +ENV GEMINI_API_KEY=$GEMINI_API_KEY +ENV OPENAI_API_KEY=$OPENAI_API_KEY +ENV ANTHROPIC_API_KEY=$ANTHROPIC_API_KEY +ENV OLLAMA_HOST=$OLLAMA_HOST +ENV OLLAMA_MODEL=$OLLAMA_MODEL +ENV OPENAI_MODEL=$OPENAI_MODEL +ENV ANTHROPIC_MODEL=$ANTHROPIC_MODEL +ENV AI_SERVICE=$AI_SERVICE # Copy source files COPY . . From f50bbf5a48c2354d2d0c6cdc9e7551e72d012d16 Mon Sep 17 00:00:00 2001 From: Rikul Patel Date: Wed, 26 Nov 2025 15:37:46 -0600 Subject: [PATCH 6/8] Update server/Dockerfile Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- server/Dockerfile | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/server/Dockerfile b/server/Dockerfile index 33a1355..3b0312f 100644 --- a/server/Dockerfile +++ b/server/Dockerfile @@ -15,6 +15,12 @@ COPY . . # Create data directory for SQLite database persistence RUN mkdir -p /data +# Create non-root user +RUN addgroup -g 1001 -S nodejs && adduser -S nodejs -u 1001 +RUN chown -R nodejs:nodejs /app /data + +# Switch to non-root user +USER nodejs # Expose port 3000 EXPOSE 3000 From 1c051385eaff54de1eec08a80df6fd1ffd13f203 Mon Sep 17 00:00:00 2001 From: Rikul Patel Date: Wed, 26 Nov 2025 15:38:39 -0600 Subject: [PATCH 7/8] Update docker-compose.yml Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- docker-compose.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 32966e6..8dce904 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -15,8 +15,6 @@ services: build: context: ./server dockerfile: Dockerfile - ports: - - "3000:3000" volumes: - billscan-data:/data environment: From e6d82ea08dbd3dfa4ae893a9f5dac9cec14cbcdf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 26 Nov 2025 21:45:00 +0000 Subject: [PATCH 8/8] Address PR review feedback: add API key build args, health check, and npm ci support Co-authored-by: Rikul <1149512+Rikul@users.noreply.github.com> --- Dockerfile | 4 ++-- README.md | 14 +++++++++++--- docker-compose.yml | 18 +++++++++++++++++- server/Dockerfile | 4 ++-- 4 files changed, 32 insertions(+), 8 deletions(-) diff --git a/Dockerfile b/Dockerfile index d8153cc..653950b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,10 +4,10 @@ FROM node:22-alpine AS builder WORKDIR /app # Copy package files -COPY package.json ./ +COPY package*.json ./ # Install dependencies -RUN npm install +RUN if [ -f package-lock.json ]; then npm ci; else npm install; fi # Accept build arguments for API keys ARG GEMINI_API_KEY diff --git a/README.md b/README.md index baa5abd..183b25e 100644 --- a/README.md +++ b/README.md @@ -54,16 +54,24 @@ If `AI_SERVICE` is not set, the application defaults to `gemini` for backward co The easiest way to run BillScan is with Docker Compose: -1. Build and start the containers: +1. Create a `.env` file in the project root with your API keys: + ```bash + # .env file + GEMINI_API_KEY=your_key_here + AI_SERVICE=gemini + # Add other keys as needed (see AI Service Configuration above) + ``` + +2. Build and start the containers: ```bash docker compose up --build ``` -2. Access the application: +3. Access the application: - Frontend: http://localhost:8080 - Backend API: http://localhost:8080/api (proxied through frontend) -3. To stop the containers: +4. To stop the containers: ```bash docker compose down ``` diff --git a/docker-compose.yml b/docker-compose.yml index 8dce904..da8b045 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,10 +3,20 @@ services: build: context: . dockerfile: Dockerfile + args: + - GEMINI_API_KEY=${GEMINI_API_KEY} + - OPENAI_API_KEY=${OPENAI_API_KEY} + - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY} + - OLLAMA_HOST=${OLLAMA_HOST} + - OLLAMA_MODEL=${OLLAMA_MODEL} + - OPENAI_MODEL=${OPENAI_MODEL} + - ANTHROPIC_MODEL=${ANTHROPIC_MODEL} + - AI_SERVICE=${AI_SERVICE} ports: - "8080:80" depends_on: - - backend + backend: + condition: service_healthy environment: - NODE_ENV=production restart: unless-stopped @@ -20,6 +30,12 @@ services: environment: - NODE_ENV=production - DATA_DIR=/data + healthcheck: + test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3000/api/bills"] + interval: 10s + timeout: 5s + retries: 3 + start_period: 10s restart: unless-stopped volumes: diff --git a/server/Dockerfile b/server/Dockerfile index 3b0312f..621fbb1 100644 --- a/server/Dockerfile +++ b/server/Dockerfile @@ -4,10 +4,10 @@ FROM node:22-alpine WORKDIR /app # Copy package files -COPY package.json ./ +COPY package*.json ./ # Install dependencies -RUN npm install --production +RUN if [ -f package-lock.json ]; then npm ci --production; else npm install --production; fi # Copy source files COPY . .