Add Docker files (and Docker compose stack) and document usage

This commit is contained in:
Johannes Schriewer 2025-01-11 04:10:20 +01:00
parent fb92e1f9c1
commit 0f12c3fa74
6 changed files with 204 additions and 22 deletions

21
Dockerfile Normal file
View file

@ -0,0 +1,21 @@
FROM python:3.13-alpine
WORKDIR /usr/src/app
RUN pip install --no-cache-dir poetry
ENV INVENTORY_STATIC_FILES=/static INVENTORY_MEDIA_FILES=/media
RUN mkdir -p "$INVENTORY_MEDIA_FILES" && mkdir -p "$INVENTORY_STATIC_FILES"
COPY pyproject.toml /usr/src/app
COPY poetry.lock /usr/src/app
RUN poetry install --no-root --no-interaction --no-cache
COPY manage.py ./manage.py
COPY inventory ./inventory
COPY inventory_project ./inventory_project
COPY entrypoint.sh /entrypoint.sh
RUN poetry run python manage.py collectstatic
CMD [ "/bin/sh", "/entrypoint.sh" ]

View file

@ -9,17 +9,19 @@ what area of your workshop you have to search for to find the part you
currently need. It has been optimized to store information for electronics
parts and small other hardware like screws, nuts and bolts.
### Prerequisites
### Prerequisites for manual install or docker Standalone
As configured by default you will need the following:
- A postgres database named `inventory` with a postgres user `inventory` that
may connect without password or by default with the password `inventory`
### Installation (manual)
You will need:
- Python > 3.10
- Poetry to install requirements and create a virtualenv
### Installation
This is a standard Django 5.1 application, if you know how to deploy those the
following might sound familiar:
@ -27,16 +29,28 @@ following might sound familiar:
- Github `git clone https://github.com/dunkelstern/inventory.git`
- ForgeJo: `git clone https://git.dunkelstern.de/dunkelstern/inventory.git`
2. Change to checkout: `cd inventory`
3. Install virtualenv and dependencies: `poetry install --no-root`
3. Install virtualenv and dependencies:
```
poetry install --no-root
```
4. If you want to use the system in another language than the default english set it
up in the `inventory_project/settings.py`:
```python
LANGUAGE_CODE = 'en-us' # or something like 'de-de'
```
see the settings file for defined languages.
5. If you changed the language rebuild the translation files: `poetry run python manage.py compilemessages`
6. Migrate the Database: `poetry run python manage.py migrate`
7. Create an admin user: `poetry run python manage.py createsuperuser`
5. If you changed the language rebuild the translation files:
```
poetry run python manage.py compilemessages
```
6. Migrate the Database:
```
poetry run python manage.py migrate
```
7. Optionally create an admin user. If not done manually the application will prompt you on first run.
```
poetry run python manage.py createsuperuser
```
8. Run the server
- Development server (not for deployment!): `poetry run python manage.py runserver`
- Deployment via `gunicorn` on port 8000: `poetry run gunicorn inventory_project.wsgi -b 0.0.0.0:8000`
@ -44,6 +58,73 @@ following might sound familiar:
Then login on `http://localhost:8000/admin/` for the Django admin interface or
go to `http://localhost:8000` to enter the inventory management system directly
### Installation (Standalone Docker)
#### Building yourself
1. Checkout repository:
- Github `git clone https://github.com/dunkelstern/inventory.git`
- ForgeJo: `git clone https://git.dunkelstern.de/dunkelstern/inventory.git`
2. Change to checkout: `cd inventory`
3. Build Docker image: `docker build -t 'dunkelstern/inventory:latest' .`
next steps below
#### Pulling from docker hub
1. Pull Docker image: `docker pull 'dunkelstern/inventory:latest'`
next steps below
#### Next steps
1. Install a PostgreSQL DB somewhere and create a user and DB.
2. Setup environment (put everything in a `.env` file):
```
INVENTORY_DB_HOST=
INVENTORY_DB_NAME=
INVENTORY_DB_USER=
INVENTORY_DB_PASSWORD=
INVENTORY_SECRET_KEY=
INVENTORY_EXTERNAL_URL=http://localhost:8000
INVENTORY_DEBUG=FALSE
INVENTORY_LANGUAGE=en-us
INVENTORY_TIMEZONE=UTC
INVENTORY_PAGE_SIZE=25
```
3. Create a media directory for uploaded files: `mkdir -p media`
4. Run the container:
```
docker run \
--name inventory \
-d \
--restart=always \
--env-file=.env \
-p 8000:8000 \
--volume ./media:/media \
dunkelstern/inventory:latest
```
5. The onboarding process will start on first call of the application and prompt to create an admin user.
### Installation (Docker compose)
1. Checkout repository:
- Github `git clone https://github.com/dunkelstern/inventory.git`
- ForgeJo: `git clone https://git.dunkelstern.de/dunkelstern/inventory.git`
2. Change to checkout: `cd inventory`
3. Copy `default.env` to `override.env` and check settings. Use a long random string for `INVENTORY_SECRET_KEY`!
4. Build the stack: `docker-compose up --build -d`
5. You can reach the application on port 8000
6. The onboarding process will start on first call of the application and prompt to create an admin user.
The compose stack will create two volumes:
- `inventory_dbdata` which contains the PostgreSQL database directory
- `inventory_mediafiles` which will contain any uploaded file
### Additional information
1. The initial DB migration pre-populates the database with some useful defaults

17
default.env Normal file
View file

@ -0,0 +1,17 @@
# override this with a long random string (used for CSRF protection)
INVENTORY_SECRET_KEY=""
# override with URL the service will be available under
INVENTORY_EXTERNAL_URL="https://inventory.example.com"
# keep this to FALSE for deployments
INVENTORY_DEBUG="FALSE"
# if you want to run the service in another language, override this
INVENTORY_LANGUAGE="en-us"
# if you want the service to use local time then override this
INVENTORY_TIMEZONE="UTC"
# This is the default pagination size
INVENTORY_PAGE_SIZE="25"

46
docker-compose.yaml Normal file
View file

@ -0,0 +1,46 @@
name: inventory
services:
db:
image: postgres:17-alpine
restart: always
shm_size: 128mb
environment:
POSTGRES_PASSWORD: inventory
POSTGRES_USER: inventory
POSTGRES_DB: inventory
volumes:
- dbdata:/var/lib/postgresql/data
inventory:
image: dunkelstern/inventory
build: .
restart: always
depends_on:
- db
env_file:
- path: ./default.env
required: true
- path: ./override.env
required: false
environment:
INVENTORY_DB_HOST: db
INVENTORY_DB_NAME: inventory
INVENTORY_DB_USER: inventory
INVENTORY_DB_PASSWORD: inventory
ports:
- name: web
target: 8000
host_ip: 127.0.0.1
published: "8000"
protocol: tcp
app_protocol: http
mode: host
volumes:
- mediafiles:/media
links:
- db
volumes:
dbdata:
mediafiles:

8
entrypoint.sh Normal file
View file

@ -0,0 +1,8 @@
#!/bin/sh
cd /usr/src/app
# poetry run python manage.py collectstatic --noinput
poetry run python manage.py migrate --noinput
exec poetry run gunicorn inventory_project.wsgi -b 0.0.0.0:8000

View file

@ -10,10 +10,12 @@ For the full list of settings and their values, see
https://docs.djangoproject.com/en/3.0/ref/settings/
"""
from typing import List
import os
import sys
import asyncio
import socket
from urllib.parse import urlparse
from uuid import uuid4
from django.utils.translation import gettext_lazy as _
if sys.platform == 'win32':
@ -23,19 +25,25 @@ if sys.platform == 'win32':
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Externally visible URL of the server
SERVER_URL = "http://127.0.0.1:8000"
SERVER_URL = os.environ.get('INVENTORY_EXTERNAL_URL', "http://127.0.0.1:8000")
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'nqo*a(^g$8#0%&+*_7#b_7ybn-znk4#=45_(qy-lq-^v675pqk'
SECRET_KEY = os.environ.get('INVENTORY_SECRET_KEY', uuid4().hex)
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS: List[str] = []
DEBUG = (os.environ.get('INVENTORY_DEBUG', "FALSE") == "TRUE")
parsed_url = urlparse(SERVER_URL)
ALLOWED_HOSTS: list[str] = [
'.localhost',
'127.0.0.1',
'[::1]',
parsed_url.hostname,
socket.gethostbyname('localhost')
]
# Application definition
@ -89,9 +97,10 @@ ASGI_APPLICATION = 'inventory_project.asgi.application'
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'inventory',
'USER': 'inventory',
'PASSWORD': 'inventory'
'HOST': os.environ.get('INVENTORY_DB_HOST', 'localhost'),
'NAME': os.environ.get('INVENTORY_DB_NAME', 'inventory'),
'USER': os.environ.get('INVENTORY_DB_USER', 'inventory'),
'PASSWORD': os.environ.get('INVENTORY_DB_PASSWORD', 'inventory')
}
}
@ -122,9 +131,9 @@ LANGUAGES = [
("en", _("English")),
]
LANGUAGE_CODE = 'en-us'
LANGUAGE_CODE = os.environ.get('INVENTORY_LANGUAGE', 'en-us')
TIME_ZONE = 'UTC'
TIME_ZONE = os.environ.get('INVENTORY_TIMEZONE', 'UTC')
USE_I18N = True
@ -137,13 +146,13 @@ USE_TZ = True
# https://docs.djangoproject.com/en/3.0/howto/static-files/
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'static')
STATIC_ROOT = os.environ.get('INVENTORY_STATIC_FILES', os.path.join(BASE_DIR, 'static'))
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
SERVE_MEDIA_FILES = DEBUG
MEDIA_ROOT = os.environ.get('INVENTORY_MEDIA_FILES', os.path.join(BASE_DIR, 'media'))
SERVE_MEDIA_FILES = True
# Default page size for paginated content
PAGE_SIZE = 25
PAGE_SIZE = int(os.environ.get('INVENTORY_PAGE_SIZE', "25"))