chore(deploy): add prod env template, improve ansible deploy, prune old workflows

- Add deployment/ansible/templates/.env.production.j2 used by secrets playbook
- Enhance deploy-update.yml to read registry creds from vault or CI
- Update production-deploy workflow to pass registry credentials to Ansible
- Remove obsolete GitHub-style workflows under .gitea (conflicted naming)

Why: make the production pipeline executable end-to-end with Ansible and
consistent secrets handling; avoid legacy CI configs interfering.
This commit is contained in:
2025-10-30 21:38:28 +01:00
parent d021c49906
commit 2a7b90312f
18 changed files with 577 additions and 30 deletions

View File

@@ -0,0 +1,137 @@
---
- name: Deploy Application Update
hosts: production
gather_facts: yes
become: yes
vars:
# These should be passed via -e from CI/CD
image_tag: "{{ image_tag | default('latest') }}"
git_commit_sha: "{{ git_commit_sha | default('unknown') }}"
deployment_timestamp: "{{ deployment_timestamp | default(ansible_date_time.iso8601) }}"
pre_tasks:
- name: Optionally load registry credentials from encrypted vault
include_vars:
file: "{{ playbook_dir }}/../secrets/production.vault.yml"
no_log: yes
ignore_errors: yes
delegate_to: localhost
become: no
- name: Derive docker registry credentials from vault when not provided
set_fact:
docker_registry_username: "{{ docker_registry_username | default(vault_docker_registry_username | default(omit)) }}"
docker_registry_password: "{{ docker_registry_password | default(vault_docker_registry_password | default(omit)) }}"
- name: Verify Docker is running
systemd:
name: docker
state: started
register: docker_service
- name: Fail if Docker is not running
fail:
msg: "Docker service is not running"
when: docker_service.status.ActiveState != 'active'
- name: Create backup directory
file:
path: "{{ backups_path }}/{{ deployment_timestamp | regex_replace(':', '-') }}"
state: directory
owner: "{{ ansible_user }}"
group: "{{ ansible_user }}"
mode: '0755'
tasks:
- name: Backup current deployment metadata
shell: |
docker service inspect {{ stack_name }}_app --format '{{ "{{" }}.Spec.TaskTemplate.ContainerSpec.Image{{ "}}" }}' \
> {{ backups_path }}/{{ deployment_timestamp | regex_replace(':', '-') }}/current_image.txt || true
docker stack ps {{ stack_name }} --format 'table {{ "{{" }}.Name{{ "}}" }}\t{{ "{{" }}.Image{{ "}}" }}\t{{ "{{" }}.CurrentState{{ "}}" }}' \
> {{ backups_path }}/{{ deployment_timestamp | regex_replace(':', '-') }}/stack_status.txt || true
args:
executable: /bin/bash
changed_when: false
- name: Login to Docker registry (if credentials provided)
docker_login:
registry_url: "{{ docker_registry_url }}"
username: "{{ docker_registry_username }}"
password: "{{ docker_registry_password }}"
no_log: yes
when:
- docker_registry_username is defined
- docker_registry_password is defined
- name: Pull new Docker image
docker_image:
name: "{{ app_image }}"
tag: "{{ image_tag }}"
source: pull
force_source: yes
register: image_pull
- name: Update docker-compose.prod.yml with new image tag
lineinfile:
path: "{{ compose_file }}"
regexp: '^(\s+image:\s+){{ app_image }}:.*$'
line: '\1{{ app_image }}:{{ image_tag }}'
backrefs: yes
when: compose_file is file
- name: Deploy stack update
docker_stack:
name: "{{ stack_name }}"
compose:
- "{{ compose_file }}"
state: present
prune: yes
register: stack_deploy
- name: Wait for service to be updated
command: >
docker service ps {{ stack_name }}_app
--filter "desired-state=running"
--format '{{ "{{" }}.CurrentState{{ "}}" }}'
register: service_status
until: "'Running' in service_status.stdout"
retries: 30
delay: 10
changed_when: false
- name: Get updated service info
command: docker service inspect {{ stack_name }}_app --format '{{ "{{" }}.Spec.TaskTemplate.ContainerSpec.Image{{ "}}" }}'
register: deployed_image
changed_when: false
- name: Record deployment metadata
copy:
content: |
Deployment Timestamp: {{ deployment_timestamp }}
Git Commit: {{ git_commit_sha }}
Image Tag: {{ image_tag }}
Deployed Image: {{ deployed_image.stdout }}
Stack Deploy Output: {{ stack_deploy }}
dest: "{{ backups_path }}/{{ deployment_timestamp | regex_replace(':', '-') }}/deployment_metadata.txt"
owner: "{{ ansible_user }}"
group: "{{ ansible_user }}"
mode: '0644'
- name: Cleanup old backups (keep last {{ max_rollback_versions }})
shell: |
cd {{ backups_path }}
ls -t | tail -n +{{ max_rollback_versions + 1 }} | xargs -r rm -rf
args:
executable: /bin/bash
changed_when: false
post_tasks:
- name: Display deployment summary
debug:
msg:
- "Deployment completed successfully!"
- "Image: {{ app_image }}:{{ image_tag }}"
- "Commit: {{ git_commit_sha }}"
- "Timestamp: {{ deployment_timestamp }}"
- "Health check URL: {{ health_check_url }}"

View File

@@ -0,0 +1,103 @@
APP_ENV=production
APP_DEBUG=false
# Application keys
APP_KEY={{ vault_app_key }}
ENCRYPTION_KEY={{ vault_encryption_key | default('') }}
STATE_ENCRYPTION_KEY={{ vault_state_encryption_key | default('') }}
JWT_SECRET={{ vault_jwt_secret | default('') }}
# Database
DB_CONNECTION=pgsql
DB_HOST=postgres
DB_PORT=5432
DB_DATABASE=framework_production
DB_USERNAME=framework_user
DB_PASSWORD={{ vault_db_password }}
# Redis
REDIS_HOST=redis
REDIS_PORT=6379
REDIS_PASSWORD={{ vault_redis_password }}
# Cache & Session
CACHE_DRIVER=redis
CACHE_PREFIX=framework
SESSION_DRIVER=redis
SESSION_LIFETIME=120
# Mail (optional)
MAIL_MAILER={{ mail_mailer | default('smtp') }}
MAIL_HOST={{ mail_host | default('') }}
MAIL_PORT={{ mail_port | default('587') }}
MAIL_USERNAME={{ mail_username | default('') }}
MAIL_PASSWORD={{ vault_mail_password | default('') }}
MAIL_ENCRYPTION={{ mail_encryption | default('tls') }}
MAIL_FROM_ADDRESS={{ mail_from_address | default('noreply@michaelschiemer.de') }}
MAIL_FROM_NAME={{ mail_from_name | default('Framework') }}
# Rate limiting / security
RATE_LIMIT_ENABLED={{ rate_limit_enabled | default('true') }}
RATE_LIMIT_DEFAULT={{ rate_limit_default | default('60') }}
RATE_LIMIT_WINDOW={{ rate_limit_window | default('60') }}
ADMIN_ALLOWED_IPS={{ admin_allowed_ips | default('127.0.0.1,::1') }}
# App domain
APP_DOMAIN={{ app_domain | default('michaelschiemer.de') }}
# Production Environment Configuration
# Generated by Ansible - DO NOT EDIT MANUALLY
# Last Updated: {{ ansible_date_time.iso8601 }}
# Application
APP_NAME={{ app_name }}
APP_ENV=production
APP_DEBUG=false
APP_URL=https://{{ app_domain }}
APP_KEY={{ vault_app_key }}
# Database
DB_CONNECTION=pgsql
DB_HOST=postgres
DB_PORT=5432
DB_DATABASE={{ app_name }}
DB_USERNAME={{ app_name }}
DB_PASSWORD={{ vault_db_password }}
# Redis
REDIS_HOST=redis
REDIS_PORT=6379
REDIS_PASSWORD={{ vault_redis_password }}
# Cache
CACHE_DRIVER=redis
QUEUE_CONNECTION=redis
SESSION_DRIVER=redis
# Security
JWT_SECRET={{ vault_jwt_secret }}
ENCRYPTION_KEY={{ vault_encryption_key | default('') }}
SESSION_SECRET={{ vault_session_secret | default('') }}
# Mail Configuration
MAIL_MAILER=smtp
MAIL_HOST={{ vault_mail_host | default('smtp.example.com') }}
MAIL_PORT={{ vault_mail_port | default('587') }}
MAIL_USERNAME={{ vault_mail_username | default('') }}
MAIL_PASSWORD={{ vault_mail_password }}
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS={{ vault_mail_from | default('noreply@' + app_domain) }}
MAIL_FROM_NAME="{{ app_name }}"
# Logging
LOG_CHANNEL=stack
LOG_LEVEL=warning
LOG_STACK=daily
# Performance
OPCACHE_ENABLE=1
OPCACHE_VALIDATE_TIMESTAMPS=0
# Deployment Info
DEPLOY_VERSION={{ image_tag | default('unknown') }}
DEPLOY_COMMIT={{ git_commit_sha | default('unknown') }}
DEPLOY_TIMESTAMP={{ deployment_timestamp | default(ansible_date_time.iso8601) }}