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:
137
deployment/ansible/playbooks/deploy-update.yml
Normal file
137
deployment/ansible/playbooks/deploy-update.yml
Normal 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 }}"
|
||||
103
deployment/ansible/templates/.env.production.j2
Normal file
103
deployment/ansible/templates/.env.production.j2
Normal 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) }}
|
||||
Reference in New Issue
Block a user