BTHLABS-63: Production deployment workflow

This commit is contained in:
2025-11-06 20:34:44 +00:00
parent d8bbe57b17
commit e800d0c16c
42 changed files with 786 additions and 97 deletions

View File

@@ -15,4 +15,4 @@ data:
./manage.py collectstatic --no-input
)
requirements.txt: |
hotpocket_bthlabs==25.10.27
hotpocket_bthlabs>=25.10.28

View File

@@ -52,6 +52,8 @@ spec:
secretKeyRef:
name: backend-vault
key: secret_id
- name: HOTPOCKET_BACKEND_CREATE_INITIAL_ACCOUNT
value: "true"
ports:
- containerPort: 8000
name: http

3
deployment/hotpocket_app/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
.ci/
inventory_ci.yaml
vault.yaml

View File

@@ -0,0 +1,5 @@
- name: "Deploy HotPocket"
hosts: "hotpocket_app"
roles:
- role: "hotpocket_app"
tags: ["hotpocket-app"]

View File

@@ -0,0 +1,3 @@
#!/usr/bin/env bash
export PIP_INDEX_URL="https://nexus.bthlabs.pl/repository/pypi/simple/"
/srv/venv/bin/pip install -r /srv/lib/backend/requirements.txt

View File

@@ -0,0 +1,5 @@
#!/usr/bin/env bash
(
cd /srv/app;
./manage.py collectstatic --no-input
)

View File

@@ -0,0 +1 @@
hotpocket-bthlabs>=25.10.28

View File

@@ -0,0 +1,60 @@
hotpocket_app:
deployment_directory: "/srv/hotpocket"
owner: "hotpocket"
group: "hotpocket"
mode: "fullstack"
loki:
url: "http://monitoring.vm.snakeweb.net.bthlabs.net:3100/loki/api/v1/push"
node: "home.vm.snakeweb.net"
docker:
extra_hosts:
- "home.vm:10.0.1.2"
backend:
image_tag: "{{ hotpocket_app_image_tag|default('deployment-v25.10.21-01') }}"
database:
name: "thisissecret"
user: "thisissecret"
host: "thisissecret"
rabbitmq:
vhost: "thisissecret"
user: "thisissecret"
host: "thisissecret"
model_auth_is_disabled: true
env: "production"
extra_env:
- "HOTPOCKET_BACKEND_SECRETS_PACKAGE=hotpocket_bthlabs.secrets"
- "VAULT_URL={{ hotpocket_app_secrets.backend.vault.url }}"
- "VAULT_ROLE_ID={{ hotpocket_app_secrets.backend.vault.role_id }}"
- "VAULT_SECRET_ID={{ hotpocket_app_secrets.backend.vault.secret_id }}"
oidc:
enabled: true
endpoint: "thisissecret"
display_name: "thisissecret"
webapp:
settings_module: "hotpocket_bthlabs.settings.webapp"
loki:
external_labels: "job=hotpocket,service=backend-webapp,environment=production"
allowed_hosts:
- "my.hotpocket.app"
admin:
settings_module: "hotpocket_bthlabs.settings.admin"
loki:
external_labels: "job=hotpocket,service=backend-admin,environment=production"
allowed_hosts:
- "admin.hotpocket.app"
celery_worker:
concurrency: 2
loki:
external_labels: "job=hotpocket,service=backend-celery-worker,environment=production"
celery_beat:
loki:
external_labels: "job=hotpocket,service=backend-celery-beat,environment=production"
customization:
- src: "{{ inventory_dir }}/env_vars/production/etc/backend/entrypoint.d/01-install-customized-deps.sh"
dest: "etc/backend/entrypoint.d/01-install-customized-deps.sh"
mode: "755"
- src: "{{ inventory_dir }}/env_vars/production/etc/backend/entrypoint.d/99-collectstatic.sh"
dest: "etc/backend/entrypoint.d/99-collectstatic.sh"
mode: "755"
- src: "{{ inventory_dir }}/env_vars/production/lib/backend/requirements.txt"
dest: "lib/backend/requirements.txt"

View File

@@ -0,0 +1,3 @@
#!/usr/bin/env bash
export PIP_INDEX_URL="https://nexus.bthlabs.pl/repository/pypi/simple/"
/srv/venv/bin/pip install -r /srv/lib/backend/requirements.txt

View File

@@ -0,0 +1,5 @@
#!/usr/bin/env bash
(
cd /srv/app;
./manage.py collectstatic --no-input
)

View File

@@ -0,0 +1 @@
hotpocket-bthlabs>=25.10.28

View File

@@ -0,0 +1,37 @@
hotpocket_app:
deployment_directory: "/srv/hotpocket_staging"
owner: "hotpocket_staging"
group: "hotpocket_staging"
mode: "aio"
loki:
url: "http://monitoring.vm.snakeweb.net.bthlabs.net:3100/loki/api/v1/push"
node: "home.vm.snakeweb.net"
docker:
extra_hosts:
- "home.vm:10.0.1.2"
backend:
image_tag: "{{ hotpocket_app_image_tag|default('aio-v25.10.29-rc1-01') }}"
model_auth_is_disabled: false
env: "staging"
extra_env:
- "HOTPOCKET_BACKEND_SECRETS_PACKAGE=hotpocket_bthlabs.secrets"
- "VAULT_URL={{ hotpocket_app_secrets.backend.vault.url }}"
- "VAULT_ROLE_ID={{ hotpocket_app_secrets.backend.vault.role_id }}"
- "VAULT_SECRET_ID={{ hotpocket_app_secrets.backend.vault.secret_id }}"
oidc:
enabled: false
webapp:
settings_module: "hotpocket_bthlabs.settings.webapp"
loki:
external_labels: "job=hotpocket,service=backend-webapp,environment=staging"
allowed_hosts:
- "staging.hotpocket.app"
customization:
- src: "{{ inventory_dir }}/env_vars/staging/etc/backend/entrypoint.d/01-install-customized-deps.sh"
dest: "etc/backend/entrypoint.d/01-install-customized-deps.sh"
mode: "755"
- src: "{{ inventory_dir }}/env_vars/staging/etc/backend/entrypoint.d/99-collectstatic.sh"
dest: "etc/backend/entrypoint.d/99-collectstatic.sh"
mode: "755"
- src: "{{ inventory_dir }}/env_vars/staging/lib/backend/requirements.txt"
dest: "lib/backend/requirements.txt"

View File

@@ -0,0 +1,10 @@
hotpocket_app:
hosts:
web1.staging.hotpocket.app:
ansible_host: vm-125.homelab01.bthlab
ansible_port: 22
ansible_user: hotpocket_staging
web1.production.hotpocket.app:
ansible_host: vm-125.homelab01.bthlab
ansible_port: 22
ansible_user: hotpocket

View File

@@ -0,0 +1,73 @@
- name: "Create workspace directories"
ansible.builtin.file:
path: "{{ hotpocket_app.deployment_directory }}/{{ item }}"
state: "directory"
loop:
- "etc"
- "etc/backend"
- "etc/backend/entrypoint.d"
- "lib"
- "lib/backend"
- "log"
- "run"
- "run/backend-admin"
- "run/backend-celery-beat"
- "run/backend-celery-worker"
- "run/backend-webapp"
- "run/uploads"
- name: "Install docker-compose.yml"
ansible.builtin.template:
src: "templates/{{ hotpocket_app.mode }}/docker-compose.yaml.jinja2"
dest: "{{ hotpocket_app.deployment_directory }}/docker-compose.yaml"
owner: "{{ hotpocket_app.owner }}"
group: "{{ hotpocket_app.group }}"
- name: "Install env files"
ansible.builtin.template:
src: "templates/{{ hotpocket_app.mode }}/{{ item }}.jinja2"
dest: "{{ hotpocket_app.deployment_directory }}/etc/{{ item }}"
owner: "{{ hotpocket_app.owner }}"
group: "{{ hotpocket_app.group }}"
loop: "{{ hotpocket_app_role.env_files[hotpocket_app.mode] }}"
- name: "Upload customization files"
ansible.builtin.copy:
src: "{{ item.src }}"
dest: "{{ hotpocket_app.deployment_directory }}/{{ item.dest }}"
owner: "{{ hotpocket_app.owner }}"
group: "{{ hotpocket_app.group }}"
mode: "{{ item.mode|default('644') }}"
loop: "{{ hotpocket_app.customization }}"
when: "hotpocket_app.customization is defined"
- name: "Install hotpocket_app.service unit"
ansible.builtin.template:
src: "templates/{{ hotpocket_app_role.services[hotpocket_app.mode].src }}.jinja2"
dest: "{{ hotpocket_app.deployment_directory }}/etc/{{ hotpocket_app_role.services[hotpocket_app.mode].dest }}"
owner: "{{ hotpocket_app.owner }}"
group: "{{ hotpocket_app.group }}"
- name: "Stop the stack"
ansible.builtin.command:
argv:
- "docker"
- "compose"
- "down"
chdir: "{{ hotpocket_app.deployment_directory }}"
- name: "Run backend migrations"
ansible.builtin.command:
argv:
- "docker"
- "compose"
- "run"
- "--rm"
- "backend-webapp"
- "./manage.py"
- "migrate"
chdir: "{{ hotpocket_app.deployment_directory }}"
when: "hotpocket_app.mode == 'fullstack' and is_manual_run is not defined"
- name: "Start the stack"
ansible.builtin.command:
argv:
- "docker"
- "compose"
- "up"
- "-d"
chdir: "{{ hotpocket_app.deployment_directory }}"
when: "is_manual_run is not defined"

View File

@@ -0,0 +1,9 @@
DJANGO_SETTINGS_MODULE="{{ hotpocket_app.backend.webapp.settings_module|default('hotpocket_backend.settings.aio')}}"
HOTPOCKET_BACKEND_ENV="{{ hotpocket_app.backend.env|default('aio') }}"
HOTPOCKET_BACKEND_MODEL_AUTH_IS_DISABLED="{% if hotpocket_app.backend.model_auth_is_disabled %}true{% else %}false{% endif %}"
{% if hotpocket_app.backend.oidc.enabled %}HOTPOCKET_BACKEND_OIDC_PAYLOAD='{"endpoint":"{{ hotpocket_app.backend.oidc.endpoint }}","key":"{{ hotpocket_app_secrets.backend.oidc.key }}","secret":"{{ hotpocket_app_secrets.backend.oidc.secret }}","display_name":"{{ hotpocket_app.backend.oidc.display_name }}"}'{% else %}#noop{% endif %}
{% for extra_env in hotpocket_app.backend.extra_env|default([]) %}
{{ extra_env }}
{% endfor %}

View File

@@ -0,0 +1,7 @@
HOTPOCKET_BACKEND_SECRET_KEY: "{{ hotpocket_app_secrets.backend.webapp.secret_key }}"
HOTPOCKET_BACKEND_ALLOWED_HOSTS="{{ hotpocket_app.backend.webapp.allowed_hosts|join(',') }}"
HOTPOCKET_BACKEND_INITIAL_ACCOUNT_USERNAME: "{{ hotpocket_app_secrets.backend.webapp.initial_account.username }}"
HOTPOCKET_BACKEND_INITIAL_ACCOUNT_PASSWORD: "{{ hotpocket_app_secrets.backend.webapp.initial_account.password }}"
{% for extra_env in hotpocket_app.backend.webapp.extra_env|default([]) %}
{{ extra_env }}
{% endfor %}

View File

@@ -0,0 +1,28 @@
services:
backend-webapp:
image: "docker-hosted.nexus.bthlabs.pl/hotpocket/backend:{{ hotpocket_app.backend.image_tag }}"
command:
- "/srv/venv/bin/gunicorn"
- "-c"
- "/srv/lib/gunicorn.conf.py"
- "-b"
- "unix:///srv/run/gunicorn.sock"
- "hotpocket_backend.wsgi:application"
logging:
driver: "loki"
options:
loki-url: "{{ hotpocket_app.loki.url }}"
loki-external-labels: "{{ hotpocket_app.backend.webapp.loki.external_labels }}"
labels: "node"
labels:
node: "{{ hotpocket_app.loki.node }}"
env_file:
- "etc/backend_base.env"
- "etc/backend_webapp.env"
extra_hosts: [{% for extra_host in hotpocket_app.docker.extra_hosts|default([]) %}"{{ extra_host }}"{% endfor %}]
restart: "unless-stopped"
volumes:
- "{{ hotpocket_app.deployment_directory }}/etc/backend:/srv/etc"
- "{{ hotpocket_app.deployment_directory }}/lib/backend:/srv/lib/backend"
- "{{ hotpocket_app.deployment_directory }}/run/backend-webapp:/srv/run"
- "{{ hotpocket_app.deployment_directory }}/run/uploads:/srv/uploads"

View File

@@ -0,0 +1,8 @@
DJANGO_SETTINGS_MODULE="{{ hotpocket_app.backend.admin.settings_module|default('hotpocket_backend.settings.deployment.admin')}}"
HOTPOCKET_BACKEND_GUNICORN_WORKERS=2
HOTPOCKET_BACKEND_APP="admin"
HOTPOCKET_BACKEND_SECRET_KEY="{{ hotpocket_app_secrets.backend.admin.secret_key }}"
HOTPOCKET_BACKEND_ALLOWED_HOSTS="{{ hotpocket_app.backend.admin.allowed_hosts|join(',') }}"
{% for extra_env in hotpocket_app.backend.admin.extra_env|default([]) %}
{{ extra_env }}
{% endfor %}

View File

@@ -0,0 +1,15 @@
HOTPOCKET_BACKEND_ENV="{{ hotpocket_app.backend.env|default('deployment') }}"
HOTPOCKET_BACKEND_DATABASE_NAME="{{ hotpocket_app.backend.database.name }}"
HOTPOCKET_BACKEND_DATABASE_USER="{{ hotpocket_app.backend.database.user }}"
HOTPOCKET_BACKEND_DATABASE_PASSWORD="{{ hotpocket_app_secrets.backend.database.password }}"
HOTPOCKET_BACKEND_DATABASE_HOST="{{ hotpocket_app.backend.database.host }}"
HOTPOCKET_BACKEND_CELERY_BROKER_URL="amqp://{{ hotpocket_app.backend.rabbitmq.user }}:{{ hotpocket_app_secrets.backend.rabbitmq.password }}@{{ hotpocket_app.backend.rabbitmq.host }}/{{ hotpocket_app.backend.rabbitmq.vhost }}"
HOTPOCKET_BACKEND_CELERY_RESULT_BACKEND="db+postgresql+psycopg://{{ hotpocket_app.backend.database.user }}:{{ hotpocket_app_secrets.backend.database.password }}@{{ hotpocket_app.backend.database.host }}/{{ hotpocket_app.backend.database.name }}"
HOTPOCKET_BACKEND_MODEL_AUTH_IS_DISABLED="{% if hotpocket_app.backend.model_auth_is_disabled %}true{% else %}false{% endif %}"
{% if hotpocket_app.backend.oidc.enabled %}HOTPOCKET_BACKEND_OIDC_PAYLOAD='{"endpoint":"{{ hotpocket_app.backend.oidc.endpoint }}","key":"{{ hotpocket_app_secrets.backend.oidc.key }}","secret":"{{ hotpocket_app_secrets.backend.oidc.secret }}","display_name":"{{ hotpocket_app.backend.oidc.display_name }}"}'{% else %}#noop{% endif %}
{% for extra_env in hotpocket_app.backend.extra_env|default([]) %}
{{ extra_env }}
{% endfor %}

View File

@@ -0,0 +1,9 @@
DJANGO_SETTINGS_MODULE="{{ hotpocket_app.backend.webapp.settings_module|default('hotpocket_backend.settings.deployment.webapp')}}"
HOTPOCKET_BACKEND_APP="webapp"
HOTPOCKET_BACKEND_SECRET_KEY="{{ hotpocket_app_secrets.backend.webapp.secret_key }}"
HOTPOCKET_BACKEND_ALLOWED_HOSTS="{{ hotpocket_app.backend.webapp.allowed_hosts|join(',') }}"
HOTPOCKET_BACKEND_SAVES_SAVE_ADAPTER="hotpocket_backend.apps.saves.adapters.postgres:PostgresSaveAdapter"
HOTPOCKET_BACKEND_SAVES_ASSOCIATION_ADAPTER="hotpocket_backend.apps.saves.adapters.postgres:PostgresAssociationAdapter"
{% for extra_env in hotpocket_app.backend.webapp.extra_env|default([]) %}
{{ extra_env }}
{% endfor %}

View File

@@ -0,0 +1,118 @@
services:
backend-webapp:
image: "docker-hosted.nexus.bthlabs.pl/hotpocket/backend:{{ hotpocket_app.backend.image_tag }}"
command:
- "/srv/venv/bin/gunicorn"
- "-c"
- "/srv/lib/gunicorn.conf.py"
- "-b"
- "unix:///srv/run/gunicorn.sock"
- "hotpocket_backend.wsgi:application"
logging:
driver: "loki"
options:
loki-url: "{{ hotpocket_app.loki.url }}"
loki-external-labels: "{{ hotpocket_app.backend.webapp.loki.external_labels }}"
labels: "node"
labels:
node: "{{ hotpocket_app.loki.node }}"
env_file:
- "etc/backend_base.env"
- "etc/backend_webapp.env"
extra_hosts: [{% for extra_host in hotpocket_app.docker.extra_hosts %}"{{ extra_host }}"{% endfor %}]
restart: "unless-stopped"
volumes:
- "{{ hotpocket_app.deployment_directory }}/etc/backend:/srv/etc"
- "{{ hotpocket_app.deployment_directory }}/lib/backend:/srv/lib/backend"
- "{{ hotpocket_app.deployment_directory }}/run/backend-webapp:/srv/run"
- "{{ hotpocket_app.deployment_directory }}/run/uploads:/srv/uploads"
backend-admin:
image: "docker-hosted.nexus.bthlabs.pl/hotpocket/backend:{{ hotpocket_app.backend.image_tag }}"
command:
- "/srv/venv/bin/gunicorn"
- "-c"
- "/srv/lib/gunicorn.conf.py"
- "-b"
- "unix:///srv/run/gunicorn.sock"
- "hotpocket_backend.wsgi:application"
logging:
driver: "loki"
options:
loki-url: "{{ hotpocket_app.loki.url }}"
loki-external-labels: "{{ hotpocket_app.backend.admin.loki.external_labels }}"
labels: "node"
labels:
node: "{{ hotpocket_app.loki.node }}"
env_file:
- "etc/backend_base.env"
- "etc/backend_admin.env"
extra_hosts: [{% for extra_host in hotpocket_app.docker.extra_hosts %}"{{ extra_host }}"{% endfor %}]
restart: "unless-stopped"
volumes:
- "{{ hotpocket_app.deployment_directory }}/etc/backend:/srv/etc"
- "{{ hotpocket_app.deployment_directory }}/lib/backend:/srv/lib/backend"
- "{{ hotpocket_app.deployment_directory }}/run/backend-admin:/srv/run"
- "{{ hotpocket_app.deployment_directory }}/run/uploads:/srv/uploads"
backend-celery-worker:
image: "docker-hosted.nexus.bthlabs.pl/hotpocket/backend:{{ hotpocket_app.backend.image_tag }}"
command:
- "/srv/venv/bin/celery"
- "-A"
- "hotpocket_backend.celery:app"
- "worker"
- "-l"
- "INFO"
- "-Q"
- "celery,webapp"
- "-c"
- "{{ hotpocket_app.backend.celery_worker.concurrency }}"
logging:
driver: "loki"
options:
loki-url: "{{ hotpocket_app.loki.url }}"
loki-external-labels: "{{ hotpocket_app.backend.celery_worker.loki.external_labels }}"
labels: "node"
labels:
node: "{{ hotpocket_app.loki.node }}"
env_file:
- "etc/backend_base.env"
- "etc/backend_webapp.env"
extra_hosts: [{% for extra_host in hotpocket_app.docker.extra_hosts %}"{{ extra_host }}"{% endfor %}]
restart: "unless-stopped"
volumes:
- "{{ hotpocket_app.deployment_directory }}/etc/backend:/srv/etc"
- "{{ hotpocket_app.deployment_directory }}/lib/backend:/srv/lib/backend"
- "{{ hotpocket_app.deployment_directory }}/run/backend-celery-worker:/srv/run"
- "{{ hotpocket_app.deployment_directory }}/run/uploads:/srv/uploads"
backend-celery-beat:
image: "docker-hosted.nexus.bthlabs.pl/hotpocket/backend:{{ hotpocket_app.backend.image_tag }}"
command:
- "/srv/venv/bin/celery"
- "-A"
- "hotpocket_backend.celery:app"
- "beat"
- "-l"
- "INFO"
- "-s"
- "/srv/run/celery-beat-schedule"
logging:
driver: "loki"
options:
loki-url: "{{ hotpocket_app.loki.url }}"
loki-external-labels: "{{ hotpocket_app.backend.celery_beat.loki.external_labels }}"
labels: "node"
labels:
node: "{{ hotpocket_app.loki.node }}"
env_file:
- "etc/backend_base.env"
- "etc/backend_webapp.env"
extra_hosts: [{% for extra_host in hotpocket_app.docker.extra_hosts %}"{{ extra_host }}"{% endfor %}]
restart: "unless-stopped"
volumes:
- "{{ hotpocket_app.deployment_directory }}/etc/backend:/srv/etc"
- "{{ hotpocket_app.deployment_directory }}/lib/backend:/srv/lib/backend"
- "{{ hotpocket_app.deployment_directory }}/run/backend-celery-beat:/srv/run"
- "{{ hotpocket_app.deployment_directory }}/run/uploads:/srv/uploads"

View File

@@ -0,0 +1,15 @@
[Unit]
Description=hotpocket_backend.webapp
Requires=docker.service
After=docker.service
[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory={{ hotpocket_app.deployment_directory }}
ExecStart=/usr/bin/docker compose up -d
ExecStop=/usr/bin/docker compose down
TimeoutStartSec=0
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,16 @@
hotpocket_app_role:
env_files:
fullstack:
- "backend_admin.env"
- "backend_base.env"
- "backend_webapp.env"
aio:
- "backend_base.env"
- "backend_webapp.env"
services:
fullstack:
src: "hotpocket_app.service"
dest: "hotpocket_app.service"
aio:
src: "hotpocket_app.service"
dest: "staging_hotpocket_app.service"