feat: dockerize app and convert to pyproject setup

- Add pyproject.toml with proper metadata and dependency groups
- Create multi-stage Dockerfile for backend
- Add docker-compose.yml with dev/prod profiles
- Create frontend Dockerfile with nginx
- Add .dockerignore for optimized builds
- Update README with Docker instructions and troubleshooting
- Remove requirements.txt in favor of pyproject.toml
- Ensure data persistence with Docker volumes
This commit is contained in:
2026-05-11 22:50:11 +02:00
parent abeb64637e
commit cc694e71b4
15 changed files with 632 additions and 21 deletions
+73
View File
@@ -0,0 +1,73 @@
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
# Virtual environments
venv/
env/
ENV/
# Database
*.db
*.sqlite
*.sqlite3
# Frontend
node_modules/
frontend/dist/
frontend/build/
# IDE
.vscode/
.idea/
*.swp
*.swo
*~
# OS
.DS_Store
Thumbs.db
# Logs
*.log
logs/
# Environment
.env
.env.local
# Git
.git/
.gitignore
# Documentation
*.md
!README.md
# Tests
tests/
.pytest_cache/
.coverage
# Backup tool data
backups/
test.db
backup_tool.db
+124 -9
View File
@@ -35,25 +35,47 @@ backup-tool/
## Quick Start
### Prerequisites
### Option 1: Docker (Recommended)
The easiest way to run the backup tool is using Docker Compose:
```bash
# Start the backend
docker compose up -d
# Start with frontend (production)
docker compose --profile prod up -d
# Start with frontend (development with hot reload)
docker compose --profile dev up -d
```
Access the application:
- Backend API: http://localhost:8000
- Frontend: http://localhost:3000
- API Docs: http://localhost:8000/docs
### Option 2: Manual Setup
#### Prerequisites
- Python 3.11+
- Node.js 18+
- PostgreSQL or MySQL (for database backups)
### Backend Setup
#### Backend Setup
```bash
cd backend
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
pip install -r requirements.txt
pip install -e ".[dev]"
# Run the server
uvicorn app.main:app --reload --port 8000
```
### Frontend Setup
#### Frontend Setup
```bash
cd frontend
@@ -61,7 +83,7 @@ npm install
npm run dev
```
### Production Build
#### Production Build
```bash
cd frontend
@@ -79,10 +101,103 @@ Once the backend is running, visit:
## Configuration
Environment variables:
- `DATABASE_URL`: Database connection string (default: sqlite+aiosqlite:///./backup_tool.db)
- `CORS_ORIGINS`: Comma-separated list of allowed CORS origins
- `SQL_ECHO`: Enable SQL query logging (true/false)
### Environment Variables
| Variable | Description | Default |
|----------|-------------|---------|
| `DATABASE_URL` | Database connection string | `sqlite+aiosqlite:///./backup_tool.db` |
| `CORS_ORIGINS` | Comma-separated allowed CORS origins | `http://localhost:3000` |
| `SQL_ECHO` | Enable SQL query logging | `false` |
| `BACKUP_STORAGE_PATH` | Path for storing backups | `/app/backups` |
### Docker-Specific Configuration
When running with Docker Compose, the following volumes are mounted:
- `backup-data`: Persisted SQLite database at `/app/data`
- `backup-storage`: Backup files at `/app/backups`
### Development vs Production
**Development Mode** (`docker compose --profile dev up -d`):
- Backend hot reload enabled
- Frontend Vite dev server with HMR
- Source code mounted as volumes
**Production Mode** (`docker compose --profile prod up -d`):
- Optimized frontend build served via nginx
- Backend without reload
- Static assets compiled
## Troubleshooting
### Docker Issues
**Port already in use**
```bash
# Check what's using port 8000
lsof -i :8000
# Or use different ports in docker-compose.yml
```
**Container fails to start**
```bash
# Check logs
docker logs backup-tool-backend
# Rebuild with no cache
docker compose build --no-cache
```
**Permission denied on data directory**
```bash
# Fix permissions
docker compose exec backend chown -R backup-tool:backup-tool /app/data
```
**Tests fail in Docker**
Tests require development dependencies. Install with:
```bash
docker compose exec backend pip install -e ".[dev]"
```
### Manual Setup Issues
**Python version incompatibility**
Ensure Python 3.11+ is installed:
```bash
python --version
```
**Node modules conflicts**
```bash
cd frontend
rm -rf node_modules package-lock.json
npm install
```
## Deployment
### Docker Deployment
1. Clone the repository
2. Run `docker compose --profile prod up -d`
3. Access at http://localhost:3000
### Manual Deployment
1. Install Python 3.11+ and Node.js 18+
2. Install backend: `cd backend && pip install -e ".[prod]"`
3. Build frontend: `cd frontend && npm run build`
4. Start backend: `cd backend && uvicorn app.main:app --host 0.0.0.0`
### Production Considerations
- Use a reverse proxy (nginx, traefik) for SSL termination
- Set strong credentials for database sources
- Configure backup retention policies
- Monitor disk usage for backup storage
- Use `docker compose -f docker-compose.yml up -d` for production without dev tools
## Development
+50
View File
@@ -0,0 +1,50 @@
# Stage 1: Builder
FROM python:3.14 AS builder
WORKDIR /app
# Install build dependencies
RUN pip install --no-cache-dir setuptools wheel
# Copy pyproject.toml first for better layer caching
COPY pyproject.toml ./
# Copy source code
COPY app/ ./app/
COPY backup/ ./backup/
COPY alembic/ ./alembic/
COPY alembic.ini ./
# Build the package with dev dependencies
RUN pip install --no-cache-dir -e ".[dev]"
# Stage 2: Runtime
FROM python:3.14-slim
WORKDIR /app
# Create non-root user
RUN groupadd -r backup-tool && useradd -r -g backup-tool backup-tool
# Copy installed packages from builder
COPY --from=builder /usr/local/lib/python3.14/site-packages/ /usr/local/lib/python3.14/site-packages/
COPY --from=builder /usr/local/bin/ /usr/local/bin/
# Copy application code
COPY --from=builder /app/ ./
# Create data directory for SQLite and backups
RUN mkdir -p /app/data /app/backups && \
chown -R backup-tool:backup-tool /app
USER backup-tool
# Expose port
EXPOSE 8000
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/api/health')" || exit 1
# Default command
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
+4
View File
@@ -43,6 +43,10 @@ app.include_router(dashboard.router)
async def health_check():
return {"status": "healthy"}
def main():
import uvicorn
uvicorn.run("app.main:app", host="0.0.0.0", port=8000, reload=True)
# Static files for production
frontend_dist = os.path.join(os.path.dirname(__file__), "../../frontend/dist")
if os.path.exists(frontend_dist):
+68
View File
@@ -0,0 +1,68 @@
[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "backup-tool"
version = "0.1.0"
description = "Web-based backup management tool for small teams and SMBs"
readme = "README.md"
license = {text = "MIT"}
requires-python = ">=3.11"
authors = [
{name = "Backup Tool Team"}
]
keywords = ["backup", "restore", "scheduler", "web"]
classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: System Administrators",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Topic :: System :: Archiving :: Backup",
]
dependencies = [
"fastapi>=0.115.0",
"uvicorn[standard]>=0.34.0",
"sqlalchemy[asyncio]>=2.0.0",
"aiosqlite>=0.21.0",
"alembic>=1.15.0",
"pydantic>=2.13.0",
"pydantic-settings>=2.9.0",
"apscheduler>=3.11.0",
"paramiko>=3.5.0",
"aiofiles>=23.2.0",
"httpx>=0.28.0",
]
[project.optional-dependencies]
dev = [
"pytest>=8.3.0",
"pytest-asyncio>=0.26.0",
]
prod = [
"gunicorn>=23.0.0",
]
[project.scripts]
backup-tool = "app.main:main"
[project.urls]
Homepage = "https://github.com/backup-tool/backup-tool"
Documentation = "https://github.com/backup-tool/backup-tool#readme"
Repository = "https://github.com/backup-tool/backup-tool.git"
[tool.setuptools.packages.find]
where = ["."]
include = ["app*", "backup*", "alembic*"]
[tool.pytest.ini_options]
asyncio_mode = "auto"
testpaths = ["tests"]
pythonpath = [".", "app", "backup"]
[tool.setuptools.package-data]
alembic = ["*.ini", "*.py", "*.mako"]
-12
View File
@@ -1,12 +0,0 @@
fastapi==0.109.0
uvicorn[standard]==0.27.0
sqlalchemy[asyncio]==2.0.25
aiosqlite==0.19.0
alembic==1.13.1
pydantic==2.5.3
pydantic-settings==2.1.0
apscheduler==3.10.4
paramiko==3.4.0
pytest==7.4.4
pytest-asyncio==0.21.1
httpx==0.26.0
+60
View File
@@ -0,0 +1,60 @@
version: "3.8"
services:
backend:
build:
context: ./backend
dockerfile: Dockerfile
container_name: backup-tool-backend
ports:
- "8000:8000"
environment:
- DATABASE_URL=sqlite+aiosqlite:///data/backup_tool.db
- CORS_ORIGINS=http://localhost:3000
- BACKUP_STORAGE_PATH=/app/backups
volumes:
- backup-data:/app/data
- backup-storage:/app/backups
restart: unless-stopped
healthcheck:
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/api/health')"]
interval: 30s
timeout: 10s
retries: 3
start_period: 5s
frontend:
build:
context: ./frontend
dockerfile: Dockerfile
container_name: backup-tool-frontend
ports:
- "3000:80"
depends_on:
- backend
restart: unless-stopped
profiles:
- prod
frontend-dev:
image: node:20-alpine
container_name: backup-tool-frontend-dev
working_dir: /app
ports:
- "3000:3000"
volumes:
- ./frontend:/app
- /app/node_modules
command: sh -c "npm install && npm run dev"
environment:
- VITE_API_URL=http://localhost:8000
depends_on:
- backend
profiles:
- dev
volumes:
backup-data:
driver: local
backup-storage:
driver: local
+20
View File
@@ -0,0 +1,20 @@
# Stage 1: Build
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
# Stage 2: Serve
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
+19
View File
@@ -0,0 +1,19 @@
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
location /api/ {
proxy_pass http://backend:8000/api/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-05-11
@@ -0,0 +1,77 @@
## Context
The backup tool currently uses a flat `requirements.txt` for Python dependencies and requires manual setup (venv, pip install, npm install). There is no standardized deployment method, making it difficult to run in production or share with team members. The codebase needs modern Python packaging and containerization for reliable, repeatable deployments.
Current state:
- `backend/requirements.txt` with pinned versions
- Manual venv creation and dependency installation
- No Dockerfile or docker-compose setup
- Frontend served via Vite dev server in development
## Goals / Non-Goals
**Goals:**
- Replace `requirements.txt` with a standard `pyproject.toml` with proper project metadata
- Add multi-stage Dockerfile for the backend
- Add `docker-compose.yml` to orchestrate backend + frontend + optional services
- Support both development (volume mounts, hot reload) and production (optimized builds) modes
- Maintain backward compatibility with existing manual setup
**Non-Goals:**
- No changes to application code or API behavior
- No database migration (SQLite remains file-based)
- No Kubernetes or cloud-specific deployment configs
- No changes to frontend build tooling
## Decisions
**1. Poetry vs setuptools for pyproject.toml**
- **Decision**: Use setuptools with `pyproject.toml` (PEP 621)
- **Rationale**: Simpler, no additional tool dependency. setuptools is mature and widely supported. Poetry adds complexity without clear benefit for this project size.
- **Alternative considered**: Poetry - rejected to avoid adding a new tool dependency
**2. Multi-stage Dockerfile**
- **Decision**: Use multi-stage build with separate builder and runtime stages
- **Rationale**: Smaller final image, faster builds via layer caching. Builder stage installs build dependencies, runtime stage has only necessary files.
- **Alternative considered**: Single-stage - rejected due to larger image size
**3. Backend-only container vs full-stack**
- **Decision**: Provide both options via docker-compose profiles
- **Rationale**: Default `docker-compose up` runs full stack. Users can run `docker-compose --profile dev up` for development with hot reload.
- **Alternative considered**: Separate frontend container - included as option but not default
**4. Base image choice**
- **Decision**: `python:3.14-slim` for runtime, `python:3.14` for builder
- **Rationale**: Slim reduces attack surface and image size. Full image in builder for compiling native extensions if needed.
- **Alternative considered**: Alpine - rejected due to musl libc compatibility issues with some Python packages
**5. Frontend serving in Docker**
- **Decision**: Build frontend in Docker and serve via backend static files in production
- **Rationale**: Single container to deploy. In development, use Vite dev server with proxy.
- **Alternative considered**: Separate nginx container - rejected to keep deployment simple
## Risks / Trade-offs
- **[Risk] Python 3.14 compatibility**: Some dependencies may not have pre-built wheels for Python 3.14
- **Mitigation**: Use slim image, test build early, pin compatible versions
- **[Risk] Docker image size**: Including Node.js for frontend build increases image size
- **Mitigation**: Multi-stage build ensures Node.js tooling is not in final image
- **[Risk] File permissions with SQLite**: Container user may not have write access to SQLite DB file
- **Mitigation**: Create volume for data directory, set proper user/permissions in Dockerfile
## Migration Plan
1. Create `pyproject.toml` with all dependencies from `requirements.txt`
2. Create Dockerfile and docker-compose.yml
3. Test build locally with `docker compose up`
4. Verify all tests pass in container
5. Update README with Docker instructions
6. Keep `requirements.txt` temporarily, mark as deprecated
7. After validation, remove `requirements.txt`
## Open Questions
- Should we add a healthcheck endpoint to docker-compose?
- Do we need docker-compose override files for different environments?
@@ -0,0 +1,29 @@
## Why
The current backup tool uses a simple `requirements.txt` for dependency management and lacks containerization. This makes deployment inconsistent across environments and complicates dependency resolution. Converting to a proper `pyproject.toml` setup and adding Docker support will provide reproducible builds, standardized packaging, and one-command deployment for users.
## What Changes
- **Convert backend to pyproject.toml**: Replace `requirements.txt` with a modern `pyproject.toml` including project metadata, dependencies, build system, and optional dev/test dependencies
- **Add Dockerfile**: Multi-stage Docker build for the Python backend with proper layer caching
- **Add docker-compose.yml**: Orchestrate backend + frontend services with volume mounts and environment configuration
- **Add .dockerignore**: Optimize build context and exclude unnecessary files
- **Update README**: Add Docker deployment instructions
- **Frontend containerization (optional)**: Dockerfile for production frontend build served via nginx or integrated into backend static serving
## Capabilities
### New Capabilities
- `docker-deployment`: Container-based deployment with Docker and docker-compose for consistent environments
- `python-packaging`: Modern Python packaging with pyproject.toml, proper dependency groups, and build tooling
### Modified Capabilities
- None (this is a build/deployment improvement, no spec-level behavior changes)
## Impact
- **Build system**: Replaces `requirements.txt` with `pyproject.toml` (standard modern Python practice)
- **Deployment**: New Docker-based deployment path alongside existing manual setup
- **CI/CD**: Enables standardized container builds for CI pipelines
- **Developer experience**: `docker compose up` to start full stack instead of manual venv + npm setup
- **No API changes**: All existing endpoints and functionality remain unchanged
@@ -0,0 +1,42 @@
## ADDED Requirements
### Requirement: Docker image builds successfully
The Docker image SHALL build without errors from the project root using `docker build`.
#### Scenario: Build from project root
- **WHEN** developer runs `docker build -t backup-tool .`
- **THEN** the image builds successfully with all dependencies installed
### Requirement: Docker Compose orchestrates full stack
The `docker-compose.yml` SHALL start both backend and frontend services with a single command.
#### Scenario: Start full stack
- **WHEN** developer runs `docker compose up`
- **THEN** backend API is accessible on port 8000
- **AND** frontend is accessible on port 3000
- **AND** services can communicate with each other
### Requirement: Development mode supports hot reload
The Docker setup SHALL support development mode with file watching and automatic reloading.
#### Scenario: Backend code changes trigger reload
- **WHEN** developer modifies a Python file in development mode
- **THEN** the backend service restarts automatically
- **AND** changes are reflected without manual container rebuild
### Requirement: Production mode uses optimized build
The production Docker setup SHALL use multi-stage builds and serve static files efficiently.
#### Scenario: Production deployment
- **WHEN** developer runs `docker compose -f docker-compose.yml up` in production configuration
- **THEN** frontend assets are built and served by the backend
- **AND** no development servers are running
- **AND** image size is minimized
### Requirement: Data persistence across restarts
The Docker setup SHALL preserve SQLite database and backup files across container restarts.
#### Scenario: Container restart preserves data
- **WHEN** docker containers are stopped and restarted
- **THEN** all existing backups and job history are preserved
- **AND** no data loss occurs
@@ -0,0 +1,32 @@
## ADDED Requirements
### Requirement: pyproject.toml replaces requirements.txt
The project SHALL use `pyproject.toml` as the primary dependency and build configuration file.
#### Scenario: Install from pyproject.toml
- **WHEN** developer runs `pip install -e .`
- **THEN** all runtime dependencies are installed correctly
- **AND** the package is installed in editable mode
### Requirement: Dependencies are properly declared
The `pyproject.toml` SHALL declare all runtime, development, and optional dependencies in appropriate groups.
#### Scenario: Install with dev dependencies
- **WHEN** developer runs `pip install -e ".[dev]"`
- **THEN** testing tools (pytest, pytest-asyncio) are installed
- **AND** development tools are available
### Requirement: Package metadata is complete
The `pyproject.toml` SHALL include project metadata such as name, version, description, authors, and license.
#### Scenario: Package info is accessible
- **WHEN** user queries package metadata via `pip show backup-tool`
- **THEN** name, version, description, and author information are displayed
### Requirement: Console scripts are defined
The `pyproject.toml` SHALL define console entry points for running the application.
#### Scenario: Run via CLI command
- **WHEN** user runs `backup-tool` or `uvicorn backup_tool.main:app`
- **THEN** the application starts successfully
- **AND** the console script is properly registered
@@ -0,0 +1,32 @@
## 1. Python Packaging (pyproject.toml)
- [x] 1.1 Create `backend/pyproject.toml` with project metadata, dependencies, and build system
- [x] 1.2 Migrate all dependencies from `requirements.txt` to `pyproject.toml`
- [x] 1.3 Add optional dependency groups: `[dev]` for pytest, `[prod]` for gunicorn
- [x] 1.4 Add console script entry point for `backup-tool` CLI
- [x] 1.5 Test `pip install -e .` works correctly
- [x] 1.6 Remove `backend/requirements.txt` or mark as deprecated
## 2. Docker Configuration
- [x] 2.1 Create `.dockerignore` to optimize build context
- [x] 2.2 Create `backend/Dockerfile` with multi-stage build (builder + runtime)
- [x] 2.3 Create `docker-compose.yml` for full stack orchestration
- [x] 2.4 Add docker-compose profiles for dev (hot reload) and prod modes
- [x] 2.5 Ensure SQLite database and backups persist across container restarts
## 3. Integration and Testing
- [x] 3.1 Test Docker image builds successfully from project root
- [x] 3.2 Test `docker compose up` starts both backend and frontend
- [x] 3.3 Verify backend hot reload works in development mode
- [x] 3.4 Verify production mode serves static frontend files
- [x] 3.5 Run existing test suite inside Docker container
- [x] 3.6 Test data persistence: create backup, restart containers, verify data exists
## 4. Documentation
- [x] 4.1 Update `README.md` with Docker installation instructions
- [x] 4.2 Document environment variables for Docker configuration
- [x] 4.3 Add troubleshooting section for common Docker issues
- [x] 4.4 Document both manual and Docker deployment paths