🐳 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
|
||||
├── comics_data.py # Comic data (edit this to add comics)
|
||||
├── requirements.txt # Python dependencies
|
||||
├── Dockerfile # Production Docker image
|
||||
├── docker-compose.yml # Docker Compose configuration
|
||||
├── .dockerignore # Docker build exclusions
|
||||
├── scripts/ # Utility scripts
|
||||
│ ├── add_comic.py # Script to add new comic entries
|
||||
│ └── generate_rss.py # Script to generate RSS feed
|
||||
@@ -108,15 +111,54 @@ This creates/updates `static/feed.rss`
|
||||
|
||||
## 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
|
||||
python -c "import secrets; print(secrets.token_hex(32))"
|
||||
```
|
||||
|
||||
### 2. Set Environment Variables
|
||||
**2. Set Environment Variables**
|
||||
|
||||
```bash
|
||||
export SECRET_KEY="generated-secret-key-from-above"
|
||||
@@ -124,7 +166,7 @@ export DEBUG=False
|
||||
export PORT=3000
|
||||
```
|
||||
|
||||
### 3. Use a Production WSGI Server
|
||||
**3. Use a Production WSGI Server**
|
||||
|
||||
**Install Gunicorn:**
|
||||
```bash
|
||||
@@ -136,17 +178,17 @@ pip install gunicorn
|
||||
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
|
||||
- Static file serving
|
||||
- Load balancing
|
||||
- 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
|
||||
- Enable HTTPS with Let's Encrypt
|
||||
- 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