From 64f3b0cdbf11cda183d1cd32a899577da164a95a Mon Sep 17 00:00:00 2001 From: krek0 Date: Sun, 3 May 2026 16:54:46 +0200 Subject: [PATCH] Add Docker support with Dockerfile and entrypoin --- .env.example | 3 + .forgejo/workflows/docker.yml | 32 +++++++++ Dockerfile | 19 +++++ README.md | 72 +++++++++++++++++++ docker-compose.yml | 40 +++++++++++ entrypoint.sh | 7 ++ photo21/settings.py | 2 +- .../commands/create_default_admin.py | 32 +++++++++ requirements.txt | 3 +- 9 files changed, 208 insertions(+), 2 deletions(-) create mode 100644 .forgejo/workflows/docker.yml create mode 100644 Dockerfile create mode 100644 docker-compose.yml create mode 100644 entrypoint.sh create mode 100644 photologue/management/commands/create_default_admin.py diff --git a/.env.example b/.env.example index 9fa2ff9..930af5b 100644 --- a/.env.example +++ b/.env.example @@ -50,3 +50,6 @@ DB_ENGINE=sqlite #DB_PASSWORD= #DB_HOST=localhost #DB_PORT=5432 + +# SQLite settings (only used when DB_ENGINE=sqlite) +#DB_PATH=/app/data/db.sqlite3 diff --git a/.forgejo/workflows/docker.yml b/.forgejo/workflows/docker.yml new file mode 100644 index 0000000..90f3a7c --- /dev/null +++ b/.forgejo/workflows/docker.yml @@ -0,0 +1,32 @@ +name: Docker + +on: + release: + types: + - published + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Set image tag + id: meta + run: echo "TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT + + - name: Login to Forgejo registry + uses: docker/login-action@v3 + with: + registry: git.sinfonie.org + username: ${{ secrets.REGISTRY_USER }} + password: ${{ secrets.REGISTRY_TOKEN }} + + - name: Build image + run: | + docker build -t git.sinfonie.org/sinfonie/photo26:${{ steps.meta.outputs.TAG }} . + + - name: Push image + run: | + docker push git.sinfonie.org/sinfonie/photo26:${{ steps.meta.outputs.TAG }} + diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..f24c177 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,19 @@ +FROM python:3.11-slim + +ENV PYTHONDONTWRITEBYTECODE=1 +ENV PYTHONUNBUFFERED=1 + +WORKDIR /app + +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +COPY . . + +# Create volume mount points +RUN mkdir -p /app/media /app/static /app/data + +EXPOSE 8000 + +RUN chmod +x entrypoint.sh +ENTRYPOINT ["./entrypoint.sh"] diff --git a/README.md b/README.md index 5878775..f54258c 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,78 @@ run and to maintain. ```./maintenance_tool.sh``` +## Docker install + +1. Create a `docker-compose.yml` (a ready-to-use file is provided in the repository): + + ```yaml + version: "3.9" + + networks: + photo26: + + services: + db: + image: postgres:16 + container_name: photo26-db + restart: unless-stopped + environment: + POSTGRES_DB: photo26 + POSTGRES_USER: photo26 + POSTGRES_PASSWORD: change-me + volumes: + - ./postgres_data:/var/lib/postgresql/data + networks: + - photo26 + + photo26: + image: git.sinfonie.org/sinfonie/photo26:latest + container_name: photo26-app + restart: unless-stopped + depends_on: + - db + environment: + DB_ENGINE: postgres + DB_NAME: photo26 + DB_USER: photo26 + DB_PASSWORD: change-me + DB_HOST: db + DB_PORT: 5432 + SECRET_KEY: change-me + EXTRA_HOSTS: photos.example.org + volumes: + - ./media:/app/media + ports: + - "8080:8000" + networks: + - photo26 + ``` + +2. Start the stack: + + ```bash + docker compose up -d + ``` + + On first start the container will run migrations and create a default admin account automatically. + +3. **Default credentials** — change these immediately after first login: + + | Field | Value | + |----------|-----------------| + | Username | `admin` | + | Password | `admin` | + | Email | `admin@localhost` | + + Admin panel: `http://localhost:8080/admin/` + +4. **Passwords to change** in `docker-compose.yml` before going to production: + - `POSTGRES_PASSWORD` / `DB_PASSWORD` — database password + - `SECRET_KEY` — Django secret key (use a long random string) + - Log in to the admin panel and change the `admin` user password + +--- + 6. *Enjoy \o/* In development, you can launch the development server using: diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..001fed7 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,40 @@ +version: "3.9" + +networks: + photo26: + +services: + db: + image: postgres:16 + container_name: photo26-db + restart: unless-stopped + environment: + POSTGRES_DB: photo26 + POSTGRES_USER: photo26 + POSTGRES_PASSWORD: change-me + volumes: + - ./postgres_data:/var/lib/postgresql/data + networks: + - photo26 + + photo26: + image: git.sinfonie.org/sinfonie/photo26:latest + container_name: photo26-app + restart: unless-stopped + depends_on: + - db + environment: + DB_ENGINE: postgres + DB_NAME: photo26 + DB_USER: photo26 + DB_PASSWORD: change-me + DB_HOST: db + DB_PORT: 5432 + SECRET_KEY: change-me + EXTRA_HOSTS: photos.example.org + volumes: + - ./media:/app/media + ports: + - "8080:8000" + networks: + - photo26 diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100644 index 0000000..fca4a2c --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,7 @@ +#!/bin/sh +set -e + +python manage.py collectstatic --noinput +python manage.py migrate --noinput +python manage.py create_default_admin +exec gunicorn photo21.wsgi:application --bind 0.0.0.0:8000 --workers 3 diff --git a/photo21/settings.py b/photo21/settings.py index a80e727..ec542ea 100644 --- a/photo21/settings.py +++ b/photo21/settings.py @@ -153,7 +153,7 @@ elif _db_engine == "sqlite": DATABASES = { "default": { "ENGINE": "django.db.backends.sqlite3", - "NAME": os.path.join(BASE_DIR, "db.sqlite3"), + "NAME": config("DB_PATH", default=os.path.join(BASE_DIR, "db.sqlite3")), "OPTIONS": { "timeout": 10, }, diff --git a/photologue/management/commands/create_default_admin.py b/photologue/management/commands/create_default_admin.py new file mode 100644 index 0000000..b9fee01 --- /dev/null +++ b/photologue/management/commands/create_default_admin.py @@ -0,0 +1,32 @@ +from django.contrib.auth import get_user_model +from django.core.management.base import BaseCommand + + +class Command(BaseCommand): + help = "Create default admin user (admin@localhost / admin) if it does not exist" + + def handle(self, *args, **kwargs): + User = get_user_model() + email = "admin@localhost" + username = "admin" + password = "admin" + + if User.objects.filter(username=username).exists(): + self.stdout.write("Default admin already exists, skipping.") + return + + user = User.objects.create_superuser(username=username, email=email, password=password) + + # Mark the email as verified via allauth + try: + from allauth.account.models import EmailAddress + EmailAddress.objects.create( + user=user, + email=email, + primary=True, + verified=True, + ) + except Exception as e: + self.stderr.write(f"Could not create allauth EmailAddress: {e}") + + self.stdout.write(f"Default admin created: {username} / {password}") diff --git a/requirements.txt b/requirements.txt index 242cbf3..56bdc65 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,5 +7,6 @@ Pillow>=6.0.0 django-debug-toolbar>=3.2.0 python-decouple>=3.6 whitenoise>=6.0 -psycopg2>=2.9 +psycopg2-binary>=2.9 requests>=2.25 +gunicorn>=21.0