🐳 docker
This commit is contained in:
42
.dockerignore
Normal file
42
.dockerignore
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
# Git
|
||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
|
||||||
|
# Python
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
*.so
|
||||||
|
.Python
|
||||||
|
env/
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
.venv
|
||||||
|
|
||||||
|
# IDEs
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Documentation
|
||||||
|
README.md
|
||||||
|
*.md
|
||||||
|
|
||||||
|
# Docker
|
||||||
|
Dockerfile
|
||||||
|
.dockerignore
|
||||||
|
docker-compose.yml
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Testing
|
||||||
|
.pytest_cache/
|
||||||
|
.coverage
|
||||||
|
htmlcov/
|
||||||
47
Dockerfile
Normal file
47
Dockerfile
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
# Production Dockerfile for Sunday Comics
|
||||||
|
|
||||||
|
FROM python:3.11-slim
|
||||||
|
|
||||||
|
# Set working directory
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Install system dependencies
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
gcc \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Create non-root user
|
||||||
|
RUN useradd -m -u 1000 appuser && chown -R appuser:appuser /app
|
||||||
|
|
||||||
|
# Copy requirements first for better caching
|
||||||
|
COPY --chown=appuser:appuser requirements.txt .
|
||||||
|
|
||||||
|
# Install Python dependencies
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt gunicorn
|
||||||
|
|
||||||
|
# Copy application code
|
||||||
|
COPY --chown=appuser:appuser . .
|
||||||
|
|
||||||
|
# Switch to non-root user
|
||||||
|
USER appuser
|
||||||
|
|
||||||
|
# Expose port
|
||||||
|
EXPOSE 3000
|
||||||
|
|
||||||
|
# Environment variables
|
||||||
|
ENV PYTHONUNBUFFERED=1 \
|
||||||
|
PORT=3000
|
||||||
|
|
||||||
|
# Health check
|
||||||
|
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
||||||
|
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:3000').read()"
|
||||||
|
|
||||||
|
# Run with Gunicorn
|
||||||
|
CMD gunicorn app:app \
|
||||||
|
--bind 0.0.0.0:${PORT} \
|
||||||
|
--workers 4 \
|
||||||
|
--threads 2 \
|
||||||
|
--worker-class gthread \
|
||||||
|
--access-logfile - \
|
||||||
|
--error-logfile - \
|
||||||
|
--log-level info
|
||||||
58
README.md
58
README.md
@@ -17,6 +17,9 @@ sunday/
|
|||||||
├── app.py # Main Flask application
|
├── app.py # Main Flask application
|
||||||
├── comics_data.py # Comic data (edit this to add comics)
|
├── comics_data.py # Comic data (edit this to add comics)
|
||||||
├── requirements.txt # Python dependencies
|
├── requirements.txt # Python dependencies
|
||||||
|
├── Dockerfile # Production Docker image
|
||||||
|
├── docker-compose.yml # Docker Compose configuration
|
||||||
|
├── .dockerignore # Docker build exclusions
|
||||||
├── scripts/ # Utility scripts
|
├── scripts/ # Utility scripts
|
||||||
│ ├── add_comic.py # Script to add new comic entries
|
│ ├── add_comic.py # Script to add new comic entries
|
||||||
│ └── generate_rss.py # Script to generate RSS feed
|
│ └── generate_rss.py # Script to generate RSS feed
|
||||||
@@ -108,15 +111,54 @@ This creates/updates `static/feed.rss`
|
|||||||
|
|
||||||
## Production Deployment
|
## Production Deployment
|
||||||
|
|
||||||
For production, you should **NOT** use Flask's built-in development server. Instead:
|
For production, you should **NOT** use Flask's built-in development server. Choose one of the following deployment methods:
|
||||||
|
|
||||||
### 1. Generate a Secure Secret Key
|
### Option 1: Docker (Recommended)
|
||||||
|
|
||||||
|
**1. Generate a secure secret key:**
|
||||||
|
```bash
|
||||||
|
python -c "import secrets; print(secrets.token_hex(32))"
|
||||||
|
```
|
||||||
|
|
||||||
|
**2. Create a `.env` file:**
|
||||||
|
```bash
|
||||||
|
SECRET_KEY=your-generated-secret-key-here
|
||||||
|
```
|
||||||
|
|
||||||
|
**3. Build and run with Docker Compose:**
|
||||||
|
```bash
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
**Or build and run manually:**
|
||||||
|
```bash
|
||||||
|
# Build the image
|
||||||
|
docker build -t sunday-comics .
|
||||||
|
|
||||||
|
# Run the container
|
||||||
|
docker run -d \
|
||||||
|
-p 3000:3000 \
|
||||||
|
-e SECRET_KEY="your-secret-key" \
|
||||||
|
-v $(pwd)/comics_data.py:/app/comics_data.py:ro \
|
||||||
|
-v $(pwd)/static/images:/app/static/images:ro \
|
||||||
|
--name sunday-comics \
|
||||||
|
sunday-comics
|
||||||
|
```
|
||||||
|
|
||||||
|
**View logs:**
|
||||||
|
```bash
|
||||||
|
docker-compose logs -f
|
||||||
|
```
|
||||||
|
|
||||||
|
### Option 2: Manual Deployment with Gunicorn
|
||||||
|
|
||||||
|
**1. Generate a Secure Secret Key**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python -c "import secrets; print(secrets.token_hex(32))"
|
python -c "import secrets; print(secrets.token_hex(32))"
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. Set Environment Variables
|
**2. Set Environment Variables**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
export SECRET_KEY="generated-secret-key-from-above"
|
export SECRET_KEY="generated-secret-key-from-above"
|
||||||
@@ -124,7 +166,7 @@ export DEBUG=False
|
|||||||
export PORT=3000
|
export PORT=3000
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. Use a Production WSGI Server
|
**3. Use a Production WSGI Server**
|
||||||
|
|
||||||
**Install Gunicorn:**
|
**Install Gunicorn:**
|
||||||
```bash
|
```bash
|
||||||
@@ -136,17 +178,17 @@ pip install gunicorn
|
|||||||
gunicorn app:app --bind 0.0.0.0:3000 --workers 4
|
gunicorn app:app --bind 0.0.0.0:3000 --workers 4
|
||||||
```
|
```
|
||||||
|
|
||||||
### 4. Use a Reverse Proxy (Recommended)
|
### Using a Reverse Proxy (Recommended)
|
||||||
|
|
||||||
Set up Nginx or another reverse proxy in front of Gunicorn for:
|
Set up Nginx or another reverse proxy in front of your app for:
|
||||||
- HTTPS/SSL termination
|
- HTTPS/SSL termination
|
||||||
- Static file serving
|
- Static file serving
|
||||||
- Load balancing
|
- Load balancing
|
||||||
- Better security
|
- Better security
|
||||||
|
|
||||||
### 5. Additional Production Considerations
|
### Additional Production Considerations
|
||||||
|
|
||||||
- Use a process manager (systemd, supervisor)
|
- Use a process manager (systemd, supervisor) for non-Docker deployments
|
||||||
- Set appropriate file permissions
|
- Set appropriate file permissions
|
||||||
- Enable HTTPS with Let's Encrypt
|
- Enable HTTPS with Let's Encrypt
|
||||||
- Consider using a CDN for static assets
|
- Consider using a CDN for static assets
|
||||||
|
|||||||
23
docker-compose.yml
Normal file
23
docker-compose.yml
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
web:
|
||||||
|
build: .
|
||||||
|
ports:
|
||||||
|
- "3000:3000"
|
||||||
|
environment:
|
||||||
|
- SECRET_KEY=${SECRET_KEY:-please-change-this-secret-key}
|
||||||
|
- PORT=3000
|
||||||
|
- DEBUG=False
|
||||||
|
volumes:
|
||||||
|
# Mount comics data for easy updates without rebuilding
|
||||||
|
- ./comics_data.py:/app/comics_data.py:ro
|
||||||
|
- ./static/images:/app/static/images:ro
|
||||||
|
- ./static/feed.rss:/app/static/feed.rss:ro
|
||||||
|
restart: unless-stopped
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:3000').read()"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 3s
|
||||||
|
retries: 3
|
||||||
|
start_period: 5s
|
||||||
Reference in New Issue
Block a user