[UPDT] DOCKER: Standardise directory structure of Docker and related files for Horilla standards (#934)
This commit is contained in:
@@ -70,12 +70,20 @@ ENV/
|
||||
node_modules/
|
||||
|
||||
# Git
|
||||
.git/
|
||||
.git
|
||||
.gitignore
|
||||
|
||||
# Docker
|
||||
Dockerfile*
|
||||
README.md
|
||||
Dockerfile
|
||||
.dockerignore
|
||||
|
||||
# Migrations cache
|
||||
**/migrations/__pycache__/
|
||||
docker-compose.yml
|
||||
.vscode
|
||||
.idea
|
||||
*.pyc
|
||||
__pycache__
|
||||
.pytest_cache
|
||||
.coverage
|
||||
*.log
|
||||
.env
|
||||
horillavenv
|
||||
node_modules
|
||||
.DS_Store
|
||||
|
||||
92
Dockerfile
92
Dockerfile
@@ -1,77 +1,49 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
FROM python:3.12-slim
|
||||
|
||||
# -------- Base image with dependencies layer --------
|
||||
FROM python:3.11-slim AS base
|
||||
|
||||
# System deps
|
||||
ENV PYTHONDONTWRITEBYTECODE=1 \
|
||||
PYTHONUNBUFFERED=1 \
|
||||
PIP_NO_CACHE_DIR=1 \
|
||||
POETRY_VIRTUALENVS_CREATE=false
|
||||
PYTHONUNBUFFERED=1
|
||||
|
||||
# Install build deps for common Python packages (incl. cairo)
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
build-essential \
|
||||
libpq-dev \
|
||||
gcc \
|
||||
curl \
|
||||
pkg-config \
|
||||
libcairo2-dev \
|
||||
libpango1.0-dev \
|
||||
libjpeg62-turbo-dev \
|
||||
zlib1g-dev \
|
||||
libxml2-dev \
|
||||
libxslt1-dev \
|
||||
# Install system dependencies
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
build-essential \
|
||||
libpq-dev \
|
||||
libjpeg-dev \
|
||||
zlib1g-dev \
|
||||
libcairo2-dev \
|
||||
libpango1.0-dev \
|
||||
libgdk-pixbuf-xlib-2.0-dev \
|
||||
libxml2-dev \
|
||||
libxslt1-dev \
|
||||
libffi-dev \
|
||||
pkg-config \
|
||||
curl \
|
||||
netcat-openbsd \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# -------- Builder for wheels --------
|
||||
FROM base AS builder
|
||||
COPY requirements.txt ./
|
||||
RUN pip wheel --wheel-dir /wheels -r requirements.txt
|
||||
|
||||
# -------- Final runtime image --------
|
||||
FROM python:3.11-slim AS runtime
|
||||
|
||||
# Create non-root user
|
||||
RUN addgroup --system app && adduser --system --ingroup app app
|
||||
|
||||
# Install runtime deps for libraries like psycopg2, cairo, etc. (minimal)
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
libpq5 \
|
||||
libcairo2 \
|
||||
libpango-1.0-0 \
|
||||
libjpeg62-turbo \
|
||||
zlib1g \
|
||||
libxml2 \
|
||||
libxslt1.1 \
|
||||
libffi8 \
|
||||
libfreetype6 \
|
||||
ghostscript \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
RUN useradd --create-home --uid 1000 appuser
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy wheels and install
|
||||
COPY --from=builder /wheels /wheels
|
||||
RUN pip install --no-index --find-links=/wheels /wheels/*
|
||||
# Install Python dependencies
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir -r requirements.txt gunicorn psycopg2-binary
|
||||
|
||||
# Copy project
|
||||
# Copy application
|
||||
COPY . .
|
||||
|
||||
# Ensure static dirs exist and are owned by app
|
||||
RUN mkdir -p /app/staticfiles /app/static_root && chown -R app:app /app
|
||||
# Set permissions
|
||||
RUN mkdir -p staticfiles media \
|
||||
&& chown -R appuser:appuser /app
|
||||
|
||||
# Gunicorn config
|
||||
ENV PORT=8000 \
|
||||
GUNICORN_CMD_ARGS="--config deploy/gunicorn.conf.py"
|
||||
|
||||
# Entrypoint
|
||||
COPY deploy/entrypoint.sh /entrypoint.sh
|
||||
# Copy entrypoint
|
||||
COPY docker/entrypoint.sh /entrypoint.sh
|
||||
RUN chmod +x /entrypoint.sh
|
||||
|
||||
USER app
|
||||
USER appuser
|
||||
|
||||
EXPOSE 8000
|
||||
|
||||
CMD ["/entrypoint.sh"]
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
CMD ["gunicorn", "horilla.wsgi:application", "--config", "docker/gunicorn.conf.py"]
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
#!/bin/sh
|
||||
set -euo pipefail
|
||||
|
||||
# Default envs
|
||||
: "${DJANGO_SETTINGS_MODULE:=horilla.settings}"
|
||||
: "${PORT:=8000}"
|
||||
: "${DATABASE_URL:=}"
|
||||
|
||||
python manage.py migrate --noinput
|
||||
python manage.py collectstatic --noinput
|
||||
|
||||
exec gunicorn horilla.wsgi:application
|
||||
@@ -1,38 +0,0 @@
|
||||
import multiprocessing
|
||||
import os
|
||||
|
||||
# Server socket
|
||||
bind = "0.0.0.0:8000"
|
||||
backlog = 2048
|
||||
|
||||
# Worker processes
|
||||
workers = max(2, min(multiprocessing.cpu_count() * 2 + 1, 8))
|
||||
worker_class = "gthread"
|
||||
threads = 4
|
||||
worker_connections = 1000
|
||||
max_requests = 1000
|
||||
max_requests_jitter = 50
|
||||
preload_app = True
|
||||
|
||||
# Timeout
|
||||
timeout = 120
|
||||
keepalive = 5
|
||||
|
||||
# Logging
|
||||
accesslog = "-"
|
||||
errorlog = "-"
|
||||
loglevel = os.getenv("LOG_LEVEL", "info")
|
||||
access_log_format = '%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s" %(D)s'
|
||||
|
||||
# Process naming
|
||||
proc_name = "horilla-hrms"
|
||||
|
||||
# Server mechanics
|
||||
pidfile = "/tmp/gunicorn.pid"
|
||||
user = None # Run as current user in container
|
||||
group = None
|
||||
tmp_upload_dir = None
|
||||
|
||||
# SSL (if needed)
|
||||
# keyfile = "/path/to/keyfile"
|
||||
# certfile = "/path/to/certfile"
|
||||
@@ -1,118 +0,0 @@
|
||||
user nginx;
|
||||
worker_processes auto;
|
||||
|
||||
error_log /var/log/nginx/error.log warn;
|
||||
pid /var/run/nginx.pid;
|
||||
|
||||
events {
|
||||
worker_connections 1024;
|
||||
use epoll;
|
||||
multi_accept on;
|
||||
}
|
||||
|
||||
http {
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
||||
'$status $body_bytes_sent "$http_referer" '
|
||||
'"$http_user_agent" "$http_x_forwarded_for"';
|
||||
|
||||
access_log /var/log/nginx/access.log main;
|
||||
|
||||
sendfile on;
|
||||
tcp_nopush on;
|
||||
tcp_nodelay on;
|
||||
keepalive_timeout 65;
|
||||
types_hash_max_size 2048;
|
||||
client_max_body_size 50M;
|
||||
|
||||
# Gzip compression
|
||||
gzip on;
|
||||
gzip_vary on;
|
||||
gzip_min_length 10240;
|
||||
gzip_proxied any;
|
||||
gzip_types
|
||||
text/plain
|
||||
text/css
|
||||
text/xml
|
||||
text/javascript
|
||||
application/x-javascript
|
||||
application/xml+rss
|
||||
application/javascript
|
||||
application/json;
|
||||
|
||||
server_tokens off;
|
||||
|
||||
# Rate limiting
|
||||
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
|
||||
limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m;
|
||||
|
||||
# Upstream for load balancing (if needed)
|
||||
upstream app {
|
||||
server web:8000 max_fails=3 fail_timeout=30s;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
|
||||
# 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;
|
||||
|
||||
# Serve static files directly
|
||||
location /static/ {
|
||||
alias /staticfiles/;
|
||||
expires 1y;
|
||||
add_header Cache-Control "public, immutable";
|
||||
access_log off;
|
||||
}
|
||||
|
||||
# Serve media files
|
||||
location /media/ {
|
||||
alias /media/;
|
||||
expires 30d;
|
||||
add_header Cache-Control "public";
|
||||
access_log off;
|
||||
}
|
||||
|
||||
# API rate limiting
|
||||
location /api/ {
|
||||
limit_req zone=api burst=20 nodelay;
|
||||
proxy_pass http://app;
|
||||
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_read_timeout 300s;
|
||||
proxy_connect_timeout 300s;
|
||||
}
|
||||
|
||||
# Login rate limiting
|
||||
location ~ ^/(login|auth)/ {
|
||||
limit_req zone=login burst=5 nodelay;
|
||||
proxy_pass http://app;
|
||||
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_read_timeout 300s;
|
||||
proxy_connect_timeout 300s;
|
||||
}
|
||||
|
||||
# Main application
|
||||
location / {
|
||||
proxy_pass http://app;
|
||||
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_read_timeout 300s;
|
||||
proxy_connect_timeout 300s;
|
||||
proxy_redirect off;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,53 +1,56 @@
|
||||
version: "3.9"
|
||||
|
||||
services:
|
||||
web:
|
||||
build: .
|
||||
ports:
|
||||
- "8000:8000"
|
||||
volumes:
|
||||
- .:/app
|
||||
- staticfiles:/app/staticfiles
|
||||
- media:/app/media
|
||||
environment:
|
||||
- DEBUG=1
|
||||
- SECRET_KEY=dev-secret-key
|
||||
- ALLOWED_HOSTS=localhost,127.0.0.1,0.0.0.0
|
||||
- CSRF_TRUSTED_ORIGINS=http://localhost:8000
|
||||
- DATABASE_URL=postgres://horilla_user:horilla_pass@db:5432/horilla_db
|
||||
- REDIS_URL=redis://:horilla_pass@redis:6379/0
|
||||
depends_on:
|
||||
- db
|
||||
- redis
|
||||
|
||||
db:
|
||||
image: postgres:16-alpine
|
||||
environment:
|
||||
POSTGRES_DB: ${POSTGRES_DB:-horilla}
|
||||
POSTGRES_USER: ${POSTGRES_USER:-horilla}
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-horilla}
|
||||
POSTGRES_DB: horilla_db
|
||||
POSTGRES_USER: horilla_user
|
||||
POSTGRES_PASSWORD: horilla_pass
|
||||
volumes:
|
||||
- db_data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER}"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
ports:
|
||||
- "5432:5432"
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD:-horilla}
|
||||
command: redis-server --appendonly yes --requirepass horilla_pass
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "--raw", "incr", "ping"]
|
||||
interval: 5s
|
||||
timeout: 3s
|
||||
retries: 5
|
||||
|
||||
web:
|
||||
build:
|
||||
context: .
|
||||
command: /entrypoint.sh
|
||||
environment:
|
||||
DJANGO_SETTINGS_MODULE: ${DJANGO_SETTINGS_MODULE:-horilla.settings}
|
||||
SECRET_KEY: ${SECRET_KEY:-change-me}
|
||||
DEBUG: ${DEBUG:-False}
|
||||
ALLOWED_HOSTS: ${ALLOWED_HOSTS:-*}
|
||||
DATABASE_URL: postgresql://${POSTGRES_USER:-horilla}:${POSTGRES_PASSWORD:-horilla}@db:5432/${POSTGRES_DB:-horilla}
|
||||
REDIS_URL: redis://:${REDIS_PASSWORD:-horilla}@redis:6379/0
|
||||
ports:
|
||||
- "8000:8000"
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
- "6379:6379"
|
||||
|
||||
nginx:
|
||||
image: nginx:alpine
|
||||
ports:
|
||||
- "80:80"
|
||||
volumes:
|
||||
- static_data:/app/staticfiles
|
||||
- staticfiles:/static:ro
|
||||
- media:/media:ro
|
||||
- ./docker/nginx.conf:/etc/nginx/nginx.conf:ro
|
||||
depends_on:
|
||||
- web
|
||||
profiles: ["production"]
|
||||
|
||||
volumes:
|
||||
db_data:
|
||||
staticfiles:
|
||||
media:
|
||||
postgres_data:
|
||||
redis_data:
|
||||
static_data:
|
||||
|
||||
20
docker/entrypoint.sh
Executable file
20
docker/entrypoint.sh
Executable file
@@ -0,0 +1,20 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
echo "Starting Horilla HR..."
|
||||
|
||||
# Wait for PostgreSQL to be ready
|
||||
echo "Waiting for PostgreSQL..."
|
||||
while ! nc -z db 5432; do
|
||||
sleep 0.1
|
||||
done
|
||||
echo "PostgreSQL is ready!"
|
||||
|
||||
# Run migrations
|
||||
python manage.py migrate --noinput
|
||||
|
||||
# Collect static files
|
||||
python manage.py collectstatic --noinput
|
||||
|
||||
echo "Starting server..."
|
||||
exec "$@"
|
||||
45
docker/gunicorn.conf.py
Normal file
45
docker/gunicorn.conf.py
Normal file
@@ -0,0 +1,45 @@
|
||||
# Gunicorn configuration for Horilla-HR
|
||||
# This file provides advanced configuration options for the WSGI server
|
||||
|
||||
import os
|
||||
import multiprocessing
|
||||
|
||||
# Bind settings
|
||||
bind = f"0.0.0.0:{os.environ.get('PORT', '8000')}"
|
||||
host = "0.0.0.0"
|
||||
port = int(os.environ.get('PORT', '8000'))
|
||||
|
||||
# Worker settings
|
||||
workers = int(os.environ.get('GUNICORN_WORKERS', max(2, min(multiprocessing.cpu_count() * 2 + 1, 8))))
|
||||
worker_class = "gthread"
|
||||
threads = 4
|
||||
worker_connections = 1000
|
||||
max_requests = 1000
|
||||
max_requests_jitter = 50
|
||||
preload_app = True
|
||||
|
||||
# Timeout settings
|
||||
timeout = 120
|
||||
keepalive = 5
|
||||
|
||||
# Logging
|
||||
accesslog = "-"
|
||||
errorlog = "-"
|
||||
loglevel = os.environ.get('GUNICORN_LOG_LEVEL', 'info')
|
||||
access_log_format = '%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s" %(D)s'
|
||||
|
||||
# Process naming
|
||||
proc_name = "horilla-hrms"
|
||||
|
||||
# Server mechanics
|
||||
pidfile = "/tmp/gunicorn.pid"
|
||||
user = None # Run as current user in container
|
||||
group = None
|
||||
tmp_upload_dir = None
|
||||
|
||||
# Development settings
|
||||
reload = os.environ.get('GUNICORN_RELOAD', 'false').lower() == 'true'
|
||||
|
||||
# SSL settings (if needed)
|
||||
# ssl_keyfile = os.environ.get('SSL_KEYFILE')
|
||||
# ssl_certfile = os.environ.get('SSL_CERTFILE')
|
||||
31
docker/nginx.conf
Normal file
31
docker/nginx.conf
Normal file
@@ -0,0 +1,31 @@
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
http {
|
||||
upstream django {
|
||||
server web:8000;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
client_max_body_size 50M;
|
||||
|
||||
location /static/ {
|
||||
alias /static/;
|
||||
expires 1y;
|
||||
}
|
||||
|
||||
location /media/ {
|
||||
alias /media/;
|
||||
}
|
||||
|
||||
location / {
|
||||
proxy_pass http://django;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user