Skip to content
Merged
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
15 changes: 15 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -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
49 changes: 49 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# 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 if [ -f package-lock.json ]; then npm ci; else npm install; fi

# 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 . .

# 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;"]
44 changes: 43 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -49,6 +50,40 @@ 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. 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
```

Copilot AI Nov 26, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Docker Quick Start section does not explain how to configure required API keys. Without API key configuration, the application will not function.

Add a note after line 59 explaining how to pass environment variables:

**Important:** Before running, 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)

Then Docker Compose will automatically load these variables.

```suggestion

   **Important:** Before running, 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)

Then Docker Compose will automatically load these variables.

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added step 1 to the Docker Quick Start section explaining how to create a .env file with API keys before running docker compose up. Commit: e6d82ea

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot apply changes based on this feedback

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was already addressed in commit e6d82ea. The README now includes step 1 in the Docker Quick Start section that explains how to create a .env file with API keys before running docker compose up. See lines 57-63 in README.md.

3. Access the application:
- Frontend: http://localhost:8080
- Backend API: http://localhost:8080/api (proxied through frontend)

4. 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:
Expand Down Expand Up @@ -146,3 +181,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`
42 changes: 42 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
services:
frontend:
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:
condition: service_healthy
environment:
- NODE_ENV=production
restart: unless-stopped
Comment on lines +2 to +22

Copilot AI Nov 26, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Docker setup is missing environment variable configuration for AI service API keys. According to the README, BillScan requires API keys (e.g., GEMINI_API_KEY, OPENAI_API_KEY, etc.) to function. These need to be passed as build arguments to the frontend Dockerfile and configured in the docker-compose.yml.

Suggested fix for docker-compose.yml frontend service:

frontend:
  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}

And in the Dockerfile, add ARG declarations before the build step and pass them as environment variables during the build.

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot apply changes based on this feedback

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added build args for all AI service API keys to the frontend service in docker-compose.yml. Users can now configure API keys via a .env file, and they'll be passed as build arguments during the Docker build. Commit: e6d82ea


backend:
build:
context: ./server
dockerfile: Dockerfile
volumes:
- billscan-data:/data
environment:
- NODE_ENV=production
- DATA_DIR=/data
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3000/api/bills"]
Comment on lines +33 to +34

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Fix backend healthcheck command for alpine base

The backend healthcheck is defined as wget --no-verbose --tries=1 --spider http://localhost:3000/api/bills, but the backend image is built on node:22-alpine (see server/Dockerfile line 2), which ships BusyBox wget that lacks the --no-verbose flag. BusyBox exits with an error on that option, so the healthcheck fails even when the API is healthy, keeping the backend in an unhealthy state and preventing the frontend from starting because depends_on waits for a healthy backend.

Useful? React with 👍 / 👎.

interval: 10s
timeout: 5s
retries: 3
start_period: 10s
restart: unless-stopped

volumes:
billscan-data:
41 changes: 41 additions & 0 deletions nginx.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;

Comment thread
Rikul marked this conversation as resolved.
# 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;

# 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";
}
}
8 changes: 8 additions & 0 deletions server/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
node_modules
*.log
npm-debug.log*
bills.db
.git
.gitignore
*.md
tmp
28 changes: 28 additions & 0 deletions server/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Backend Dockerfile - Lightweight Node.js image
FROM node:22-alpine

WORKDIR /app

# Copy package files
COPY package*.json ./

# Install dependencies
RUN if [ -f package-lock.json ]; then npm ci --production; else npm install --production; fi

# Copy source files
COPY . .

# Create data directory for SQLite database persistence
RUN mkdir -p /data

Comment thread
Rikul marked this conversation as resolved.
# 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

# Start the server
CMD ["node", "index.js"]
4 changes: 3 additions & 1 deletion server/db.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
});

Expand Down