-
Notifications
You must be signed in to change notification settings - Fork 0
feat(infra): add dockerfile, compose setup and migrate containerized #2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
8b30105
a9e986e
06ead23
0e09a59
0c87ae3
bb8e4a0
0ea4689
874453b
7a62da5
42ae96e
36badfa
bd8be10
2b12b3b
b19be04
a28398e
8612660
b6ba86d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| # Ignore VCS | ||
| .git | ||
| .gitignore | ||
|
|
||
| # Python artifacts | ||
| __pycache__/ | ||
| *.py[cod] | ||
| *.pyc | ||
| *.pyo | ||
| *.pyd | ||
| *.so | ||
|
|
||
| # Local env & tooling | ||
| .env | ||
| .env.* | ||
| venv/ | ||
| .virtualenv/ | ||
| .cache/ | ||
| .mypy_cache/ | ||
| .pytest_cache/ | ||
| .coverage | ||
|
|
||
| # Django/SQLite development DB (use real DB in production) | ||
| db.sqlite3 | ||
|
|
||
| # Node / misc | ||
| node_modules/ | ||
|
|
||
| # Ansible deployment artifacts (not needed in runtime image) | ||
| ansible/ | ||
|
|
||
| # Other | ||
| *.log |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,13 @@ | ||
| DJANGO_DEBUG=True | ||
| DJANGO_SECRET_KEY=change-me | ||
| DJANGO_DEBUG=false | ||
| DJANGO_SETTINGS_MODULE=config.settings | ||
| DJANGO_STATIC_ROOT=/app/static/ | ||
| PORT=8000 | ||
| GUNICORN_WORKERS=3 | ||
| IMAGE_NAME=histrio/idontneedit:latest | ||
| SITE_DOMAIN=idontneedit.org.ru | ||
| POSTGRES_DB=idontneedit | ||
| POSTGRES_USER=idontneedit | ||
| POSTGRES_PASSWORD=changeme | ||
| POSTGRES_HOST=postgres | ||
| POSTGRES_PORT=5432 | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -25,7 +25,6 @@ jobs: | |||||
| - name: Install uv | ||||||
| run: pip install uv | ||||||
|
|
||||||
|
|
||||||
| - name: Install project | ||||||
| run: uv sync | ||||||
|
|
||||||
|
|
@@ -38,11 +37,40 @@ jobs: | |||||
| - name: Run tests | ||||||
| run: uv run python manage.py test | ||||||
|
|
||||||
| deploy: | ||||||
| build-image: | ||||||
| needs: build | ||||||
| if: github.ref == 'refs/heads/main' | ||||||
| runs-on: ubuntu-latest | ||||||
|
|
||||||
| steps: | ||||||
| - name: checkout | ||||||
| uses: actions/checkout@v4 | ||||||
|
|
||||||
| - name: set up Docker Buildx | ||||||
| uses: docker/setup-buildx-action@v3 | ||||||
|
|
||||||
| - name: log in to Docker Hub | ||||||
| uses: docker/login-action@v3 | ||||||
| with: | ||||||
| username: ${{ secrets.DOCKER_USERNAME }} | ||||||
| password: ${{ secrets.DOCKER_PASSWORD }} | ||||||
|
|
||||||
| - name: build and push Docker image | ||||||
| uses: docker/build-push-action@v5 | ||||||
|
||||||
| uses: docker/build-push-action@v5 | |
| uses: docker/build-push-action@v6 |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -6,6 +6,12 @@ repos: | |||||
| - id: end-of-file-fixer | ||||||
| - id: trailing-whitespace | ||||||
| - id: check-added-large-files | ||||||
| - id: forbid-new-submodules | ||||||
| - id: no-commit-to-branch | ||||||
| args: ['--branch', 'main', '--branch', 'master'] | ||||||
| - id: check-case-conflict | ||||||
| - id: check-merge-conflict | ||||||
| - id: detect-private-key | ||||||
| - repo: https://github.com/psf/black | ||||||
| rev: 25.11.0 | ||||||
| hooks: | ||||||
|
|
@@ -20,3 +26,11 @@ repos: | |||||
| rev: v1.4.0 | ||||||
| hooks: | ||||||
| - id: detect-secrets | ||||||
|
|
||||||
| - repo: local | ||||||
| hooks: | ||||||
| - id: forbid-env-files | ||||||
| name: Forbid .env files | ||||||
| entry: '.env files must not be committed' | ||||||
| language: fail | ||||||
| files: '(^|/)\.env($|\.(?!example$).*)' | ||||||
|
||||||
| files: '(^|/)\.env($|\.(?!example$).*)' | |
| files: '^\.env$|^\.env\.(?!example$).+' |
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,48 @@ | ||||||||||
| ARG PYTHON_VERSION=3.13 | ||||||||||
| FROM python:${PYTHON_VERSION}-slim AS base | ||||||||||
|
|
||||||||||
| ENV DEBIAN_FRONTEND=noninteractive \ | ||||||||||
| PYTHONDONTWRITEBYTECODE=1 \ | ||||||||||
| PYTHONUNBUFFERED=1 \ | ||||||||||
| PIP_NO_CACHE_DIR=off \ | ||||||||||
| PIP_DISABLE_PIP_VERSION_CHECK=on \ | ||||||||||
| PIP_DEFAULT_TIMEOUT=100 | ||||||||||
|
|
||||||||||
| RUN apt-get update \ | ||||||||||
| && apt-get install -y --no-install-recommends \ | ||||||||||
| ca-certificates curl \ | ||||||||||
| && rm -rf /var/lib/apt/lists/* | ||||||||||
|
|
||||||||||
| RUN useradd --uid 10001 --create-home --shell /usr/sbin/nologin app \ | ||||||||||
|
||||||||||
| RUN useradd --uid 10001 --create-home --shell /usr/sbin/nologin app \ | |
| RUN useradd --uid 10001 --create-home --shell /bin/false app \ |
Copilot
AI
Dec 2, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The environment variables GUNICORN_BIND and GUNICORN_MAX_REQUESTS are defined but never used in the CMD directive. The CMD at line 48 only uses PORT and GUNICORN_WORKERS. Either use these variables in the command or remove them:
CMD ["/bin/sh", "-c", "exec gunicorn config.wsgi:application --bind ${GUNICORN_BIND:-0.0.0.0:8000} --workers ${GUNICORN_WORKERS:-3} --max-requests ${GUNICORN_MAX_REQUESTS:-1000} --max-requests-jitter ${GUNICORN_MAX_REQUESTS_JITTER:-100} --access-logfile - --error-logfile -"]
Copilot
AI
Dec 2, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The COPY . /app command at line 37 happens after pip install, which is good for cache efficiency. However, copying everything and then changing ownership with chown -R creates an additional layer. Consider combining these operations or copying with the correct ownership:
COPY --chown=app:app . /appThis reduces the image size by avoiding a duplicate layer.
| COPY . /app | |
| RUN chown -R app:app /app | |
| COPY --chown=app:app . /app |
Copilot
AI
Dec 1, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The CMD uses shell expansion for environment variables which is correct, but this could be simplified using the default environment values already set on lines 30-31. Consider referencing $GUNICORN_BIND directly or remove the defaults here to avoid duplication with ENV declarations.
| CMD ["/bin/sh", "-c", "exec gunicorn config.wsgi:application --bind 0.0.0.0:${PORT:-8000} --workers ${GUNICORN_WORKERS:-3} --access-logfile - --error-logfile -"] | |
| CMD ["/bin/sh", "-c", "exec gunicorn config.wsgi:application --bind 0.0.0.0:$PORT --workers $GUNICORN_WORKERS --access-logfile - --error-logfile -"] |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -15,3 +15,17 @@ requirements.txt: uv.lock | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @test -r .env \ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| && echo "Your .env is older than .env.example" \ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| || cp .env.example .env | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| DOCKER?=docker | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| IMAGE_REPO:=histrio/idontneedit | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| IMAGE_TAG?=$(shell git rev-parse --short HEAD) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| LATEST_TAG?=latest | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .PHONY: image.build | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| image.build: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @$(DOCKER) build -t $(IMAGE_REPO):$(IMAGE_TAG) -t $(IMAGE_REPO):$(LATEST_TAG) . | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .PHONY: image.push | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| image.push: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @$(DOCKER) push $(IMAGE_REPO):$(IMAGE_TAG) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @$(DOCKER) push $(IMAGE_REPO):$(LATEST_TAG) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+21
to
+31
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| IMAGE_TAG?=$(shell git rev-parse --short HEAD) | |
| LATEST_TAG?=latest | |
| .PHONY: image.build | |
| image.build: | |
| @$(DOCKER) build -t $(IMAGE_REPO):$(IMAGE_TAG) -t $(IMAGE_REPO):$(LATEST_TAG) . | |
| .PHONY: image.push | |
| image.push: | |
| @$(DOCKER) push $(IMAGE_REPO):$(IMAGE_TAG) | |
| @$(DOCKER) push $(IMAGE_REPO):$(LATEST_TAG) | |
| # NOTE: The image.build and image.push targets require git to be installed and the directory to be a git repository. | |
| IMAGE_TAG?=$(shell git rev-parse --short HEAD) | |
| LATEST_TAG?=latest | |
| .PHONY: image.build | |
| image.build: | |
| @if ! command -v git >/dev/null 2>&1; then \ | |
| echo "Error: git is not installed. Cannot determine IMAGE_TAG."; exit 1; \ | |
| fi; \ | |
| if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then \ | |
| echo "Error: Not a git repository. Cannot determine IMAGE_TAG."; exit 1; \ | |
| fi; \ | |
| $(DOCKER) build -t $(IMAGE_REPO):$(IMAGE_TAG) -t $(IMAGE_REPO):$(LATEST_TAG) . | |
| .PHONY: image.push | |
| image.push: | |
| @if ! command -v git >/dev/null 2>&1; then \ | |
| echo "Error: git is not installed. Cannot determine IMAGE_TAG."; exit 1; \ | |
| fi; \ | |
| if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then \ | |
| echo "Error: Not a git repository. Cannot determine IMAGE_TAG."; exit 1; \ | |
| fi; \ | |
| $(DOCKER) push $(IMAGE_REPO):$(IMAGE_TAG) | |
| $(DOCKER) push $(IMAGE_REPO):$(LATEST_TAG) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,15 +1,18 @@ | ||
| idontneedit.org.ru { | ||
| {$SITE_DOMAIN} { | ||
|
||
| encode zstd gzip | ||
|
|
||
|
|
||
| handle_path /static/* { | ||
| root * /srv/idontneedit/static | ||
| file_server | ||
| header { | ||
| Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" | ||
| X-Content-Type-Options "nosniff" | ||
| X-Frame-Options "DENY" | ||
| Referrer-Policy "strict-origin-when-cross-origin" | ||
| } | ||
|
|
||
| handle_path /media/* { | ||
| root * /srv/idontneedit/media | ||
| handle_path /static/* { | ||
| root * /app/static | ||
| header Cache-Control "public, max-age=31536000, immutable" | ||
| file_server | ||
| } | ||
|
|
||
| reverse_proxy unix//srv/idontneedit/run/gunicorn.sock | ||
| reverse_proxy app:8000 | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| [ssh_connection] | ||
| ssh_args = -o ControlMaster=auto -o ControlPersist=60s | ||
| pipelining = True |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The default value for
DJANGO_DEBUGis inconsistent between files. In.env.exampleit'sfalse(string), butdjango-environexpects a boolean. While django-environ can parse string values like "false", "False", "0", etc., using "true" or "false" strings is clearer. However, inconfig/settings.pyline 23,env.bool()is used which properly handles this. Consider documenting that this should be "true" or "false" (lowercase) for consistency.