Some checks failed
🚀 Build & Deploy Image / Determine Build Necessity (push) Failing after 10m14s
🚀 Build & Deploy Image / Build Runtime Base Image (push) Has been skipped
🚀 Build & Deploy Image / Build Docker Image (push) Has been skipped
🚀 Build & Deploy Image / Run Tests & Quality Checks (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Staging (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Production (push) Has been skipped
Security Vulnerability Scan / Check for Dependency Changes (push) Failing after 11m25s
Security Vulnerability Scan / Composer Security Audit (push) Has been cancelled
- Remove middleware reference from Gitea Traefik labels (caused routing issues) - Optimize Gitea connection pool settings (MAX_IDLE_CONNS=30, authentication_timeout=180s) - Add explicit service reference in Traefik labels - Fix intermittent 504 timeouts by improving PostgreSQL connection handling Fixes Gitea unreachability via git.michaelschiemer.de
433 lines
16 KiB
YAML
433 lines
16 KiB
YAML
---
|
|
- name: Build and Push Initial Docker Image
|
|
hosts: production
|
|
become: no
|
|
gather_facts: yes
|
|
|
|
vars:
|
|
vault_file: "{{ playbook_dir }}/../secrets/production.vault.yml"
|
|
build_repo_path: "/home/deploy/michaelschiemer"
|
|
build_repo_url: "{{ git_repository_url_default | default('https://git.michaelschiemer.de/michael/michaelschiemer.git') }}"
|
|
build_repo_branch_default: "main"
|
|
# Local repository path for cloning (temporary)
|
|
local_repo_path: "/tmp/michaelschiemer-build-{{ ansible_date_time.epoch }}"
|
|
# Check if local repository exists (project root)
|
|
local_repo_check_path: "{{ playbook_dir | default('') | dirname | dirname | dirname }}"
|
|
image_name: "{{ app_name | default('framework') }}"
|
|
image_tag_default: "latest"
|
|
registry_url: "{{ docker_registry | default('localhost:5000') }}"
|
|
registry_username: "{{ vault_docker_registry_username | default(docker_registry_username_default | default('admin')) }}"
|
|
registry_password: "{{ vault_docker_registry_password | default('') }}"
|
|
|
|
pre_tasks:
|
|
- name: Verify vault file exists
|
|
ansible.builtin.stat:
|
|
path: "{{ vault_file }}"
|
|
register: vault_stat
|
|
delegate_to: localhost
|
|
become: no
|
|
|
|
- name: Load vault secrets
|
|
ansible.builtin.include_vars:
|
|
file: "{{ vault_file }}"
|
|
when: vault_stat.stat.exists
|
|
no_log: yes
|
|
ignore_errors: yes
|
|
delegate_to: localhost
|
|
become: no
|
|
|
|
- name: Verify registry password is set
|
|
ansible.builtin.fail:
|
|
msg: |
|
|
Registry password is required!
|
|
|
|
Please set vault_docker_registry_password in:
|
|
{{ vault_file }}
|
|
|
|
Or pass it via extra vars:
|
|
-e "registry_password=your-password"
|
|
when: registry_password | string | trim == ''
|
|
|
|
tasks:
|
|
- name: Set build variables
|
|
ansible.builtin.set_fact:
|
|
build_repo_branch: "{{ build_repo_branch | default(build_repo_branch_default) }}"
|
|
image_tag: "{{ build_image_tag | default(image_tag_default) }}"
|
|
|
|
- name: Display build information
|
|
ansible.builtin.debug:
|
|
msg: |
|
|
Building Docker Image:
|
|
- Repository: {{ build_repo_url }}
|
|
- Branch: {{ build_repo_branch }}
|
|
- Build Path: {{ build_repo_path }}
|
|
- Registry: {{ registry_url }}
|
|
- Image: {{ image_name }}:{{ image_tag }}
|
|
- Username: {{ registry_username }}
|
|
|
|
- name: Check if local repository exists (project root)
|
|
ansible.builtin.stat:
|
|
path: "{{ local_repo_check_path }}/.git"
|
|
delegate_to: localhost
|
|
register: local_repo_exists
|
|
become: no
|
|
|
|
- name: Configure Git to skip SSL verification for git.michaelschiemer.de (local)
|
|
ansible.builtin.command: |
|
|
git config --global http.https://git.michaelschiemer.de.sslVerify false
|
|
delegate_to: localhost
|
|
changed_when: false
|
|
failed_when: false
|
|
become: no
|
|
when: not local_repo_exists.stat.exists
|
|
|
|
- name: Determine Git URL with authentication if token is available
|
|
ansible.builtin.set_fact:
|
|
git_repo_url_with_auth: >-
|
|
{%- if vault_git_token is defined and vault_git_token | string | trim != '' -%}
|
|
https://{{ vault_git_token }}@git.michaelschiemer.de/michael/michaelschiemer.git
|
|
{%- elif vault_git_username is defined and vault_git_username | string | trim != '' and vault_git_password is defined and vault_git_password | string | trim != '' -%}
|
|
https://{{ vault_git_username }}:{{ vault_git_password }}@git.michaelschiemer.de/michael/michaelschiemer.git
|
|
{%- else -%}
|
|
{{ build_repo_url }}
|
|
{%- endif -%}
|
|
no_log: yes
|
|
|
|
- name: Debug Git URL (without credentials)
|
|
ansible.builtin.debug:
|
|
msg: |
|
|
Git Repository Configuration:
|
|
- Original URL: {{ build_repo_url }}
|
|
- Local repo exists: {{ 'YES' if local_repo_exists.stat.exists else 'NO' }}
|
|
- Local repo path: {{ local_repo_check_path }}
|
|
- Using authentication: {{ 'YES' if (vault_git_token is defined and vault_git_token | string | trim != '') or (vault_git_username is defined and vault_git_username | string | trim != '') else 'NO' }}
|
|
- Auth method: {{ 'Token' if (vault_git_token is defined and vault_git_token | string | trim != '') else 'Username/Password' if (vault_git_username is defined and vault_git_username | string | trim != '') else 'None' }}
|
|
no_log: yes
|
|
|
|
- name: Use existing local repository or clone to temporary location
|
|
block:
|
|
- name: Clone repository to temporary location (local)
|
|
ansible.builtin.git:
|
|
repo: "{{ git_repo_url_with_auth }}"
|
|
dest: "{{ local_repo_path }}"
|
|
version: "{{ build_repo_branch }}"
|
|
force: yes
|
|
update: yes
|
|
delegate_to: localhost
|
|
register: git_result
|
|
changed_when: git_result.changed
|
|
environment:
|
|
GIT_SSL_NO_VERIFY: "1"
|
|
no_log: yes
|
|
when: not local_repo_exists.stat.exists
|
|
|
|
- name: Set local repository path to existing project root
|
|
ansible.builtin.set_fact:
|
|
source_repo_path: "{{ local_repo_check_path }}"
|
|
when: local_repo_exists.stat.exists
|
|
|
|
- name: Set local repository path to cloned temporary location
|
|
ansible.builtin.set_fact:
|
|
source_repo_path: "{{ local_repo_path }}"
|
|
when: not local_repo_exists.stat.exists
|
|
|
|
- name: Display repository source
|
|
ansible.builtin.debug:
|
|
msg: |
|
|
Repository source:
|
|
- Using existing local repo: {{ 'YES' if local_repo_exists.stat.exists else 'NO' }}
|
|
- Source path: {{ source_repo_path }}
|
|
- Branch: {{ build_repo_branch }}
|
|
|
|
- name: Ensure build directory exists on server
|
|
ansible.builtin.file:
|
|
path: "{{ build_repo_path }}"
|
|
state: directory
|
|
mode: '0755'
|
|
owner: "{{ ansible_user }}"
|
|
group: "{{ ansible_user }}"
|
|
|
|
- name: Copy repository to server (excluding .git and build artifacts)
|
|
ansible.builtin.synchronize:
|
|
src: "{{ source_repo_path }}/"
|
|
dest: "{{ build_repo_path }}/"
|
|
delete: no
|
|
recursive: yes
|
|
rsync_opts:
|
|
- "--chmod=D755,F644"
|
|
- "--exclude=.git"
|
|
- "--exclude=.gitignore"
|
|
- "--exclude=node_modules"
|
|
- "--exclude=vendor"
|
|
- "--exclude=.env"
|
|
- "--exclude=.env.*"
|
|
- "--exclude=*.log"
|
|
- "--exclude=.idea"
|
|
- "--exclude=.vscode"
|
|
- "--exclude=.DS_Store"
|
|
- "--exclude=*.swp"
|
|
- "--exclude=*.swo"
|
|
- "--exclude=*~"
|
|
- "--exclude=.phpunit.result.cache"
|
|
- "--exclude=coverage"
|
|
- "--exclude=.phpunit.cache"
|
|
- "--exclude=public/assets"
|
|
- "--exclude=storage/logs"
|
|
- "--exclude=storage/framework/cache"
|
|
- "--exclude=storage/framework/sessions"
|
|
- "--exclude=storage/framework/views"
|
|
|
|
- name: Clean up temporary cloned repository
|
|
ansible.builtin.file:
|
|
path: "{{ local_repo_path }}"
|
|
state: absent
|
|
delegate_to: localhost
|
|
become: no
|
|
when:
|
|
- not local_repo_exists.stat.exists
|
|
- local_repo_path is defined
|
|
|
|
- name: Check if Dockerfile.production exists on server
|
|
ansible.builtin.stat:
|
|
path: "{{ build_repo_path }}/Dockerfile.production"
|
|
register: dockerfile_stat
|
|
|
|
- name: Fail if Dockerfile.production not found
|
|
ansible.builtin.fail:
|
|
msg: |
|
|
Dockerfile.production not found at {{ build_repo_path }}/Dockerfile.production
|
|
|
|
Please verify:
|
|
1. The repository was copied successfully to {{ build_repo_path }}
|
|
2. The Dockerfile.production file exists in the repository
|
|
3. The source repository path is correct: {{ source_repo_path }}
|
|
when: not dockerfile_stat.stat.exists
|
|
|
|
- name: Check if entrypoint script exists on server
|
|
ansible.builtin.stat:
|
|
path: "{{ build_repo_path }}/docker/entrypoint.sh"
|
|
register: entrypoint_stat
|
|
|
|
- name: Display entrypoint script status
|
|
ansible.builtin.debug:
|
|
msg: |
|
|
Entrypoint Script Check:
|
|
- Path: {{ build_repo_path }}/docker/entrypoint.sh
|
|
- Exists: {{ entrypoint_stat.stat.exists | default(false) }}
|
|
{% if entrypoint_stat.stat.exists %}
|
|
- Mode: {{ entrypoint_stat.stat.mode | default('unknown') }}
|
|
- Size: {{ entrypoint_stat.stat.size | default(0) }} bytes
|
|
{% endif %}
|
|
when: not ansible_check_mode
|
|
|
|
- name: Fail if entrypoint script not found
|
|
ansible.builtin.fail:
|
|
msg: |
|
|
Entrypoint script not found at {{ build_repo_path }}/docker/entrypoint.sh
|
|
|
|
This file is required for the Docker image build!
|
|
|
|
Please verify:
|
|
1. The file exists in the source repository: {{ source_repo_path }}/docker/entrypoint.sh
|
|
2. The rsync operation copied the file successfully
|
|
when: not entrypoint_stat.stat.exists
|
|
|
|
- name: Convert entrypoint script to LF line endings
|
|
ansible.builtin.shell: |
|
|
sed -i 's/\r$//' "{{ build_repo_path }}/docker/entrypoint.sh"
|
|
when: entrypoint_stat.stat.exists
|
|
changed_when: false
|
|
failed_when: false
|
|
|
|
- name: Verify entrypoint script has LF line endings
|
|
ansible.builtin.shell: |
|
|
if head -1 "{{ build_repo_path }}/docker/entrypoint.sh" | od -c | grep -q "\\r"; then
|
|
echo "CRLF_DETECTED"
|
|
else
|
|
echo "LF_ONLY"
|
|
fi
|
|
register: line_ending_check
|
|
changed_when: false
|
|
when: entrypoint_stat.stat.exists
|
|
|
|
- name: Display line ending check result
|
|
ansible.builtin.debug:
|
|
msg: |
|
|
Entrypoint Script Line Endings:
|
|
- Status: {{ line_ending_check.stdout | default('unknown') }}
|
|
{% if 'CRLF_DETECTED' in line_ending_check.stdout %}
|
|
⚠️ WARNING: Script still has CRLF line endings after conversion attempt!
|
|
{% else %}
|
|
✅ Script has LF line endings
|
|
{% endif %}
|
|
when:
|
|
- entrypoint_stat.stat.exists
|
|
- not ansible_check_mode
|
|
|
|
- name: Login to Docker registry
|
|
community.docker.docker_login:
|
|
registry_url: "{{ registry_url }}"
|
|
username: "{{ registry_username }}"
|
|
password: "{{ registry_password }}"
|
|
no_log: yes
|
|
register: login_result
|
|
|
|
- name: Verify registry login
|
|
ansible.builtin.debug:
|
|
msg: "✅ Successfully logged in to {{ registry_url }}"
|
|
when: not login_result.failed | default(false)
|
|
|
|
- name: Fail if registry login failed
|
|
ansible.builtin.fail:
|
|
msg: |
|
|
Failed to login to Docker registry!
|
|
|
|
Registry: {{ registry_url }}
|
|
Username: {{ registry_username }}
|
|
|
|
Please verify:
|
|
1. The registry is running and accessible
|
|
2. The username and password are correct
|
|
3. The registry URL is correct
|
|
when: login_result.failed | default(false)
|
|
|
|
- name: Verify Docker Buildx is available
|
|
ansible.builtin.command: docker buildx version
|
|
register: buildx_check
|
|
changed_when: false
|
|
failed_when: false
|
|
|
|
- name: Warn if Buildx is not available
|
|
ansible.builtin.debug:
|
|
msg: |
|
|
⚠️ Docker Buildx not found. BuildKit features may not work.
|
|
Install with: apt-get install docker-buildx-plugin
|
|
when: buildx_check.rc != 0
|
|
|
|
- name: Set build cache option
|
|
ansible.builtin.set_fact:
|
|
build_no_cache: "{{ build_no_cache | default('false') | bool }}"
|
|
|
|
- name: Build and push Docker image with BuildKit
|
|
ansible.builtin.shell: |
|
|
set -e
|
|
BUILD_ARGS=""
|
|
{% if build_no_cache | bool %}
|
|
BUILD_ARGS="--no-cache"
|
|
{% endif %}
|
|
DOCKER_BUILDKIT=1 docker buildx build \
|
|
--platform linux/amd64 \
|
|
--file {{ build_repo_path }}/Dockerfile.production \
|
|
--tag {{ registry_url }}/{{ image_name }}:{{ image_tag }} \
|
|
--push \
|
|
--progress=plain \
|
|
$BUILD_ARGS \
|
|
{{ build_repo_path }}
|
|
register: build_result
|
|
environment:
|
|
DOCKER_BUILDKIT: "1"
|
|
changed_when: build_result.rc == 0
|
|
failed_when: build_result.rc != 0
|
|
async: 3600
|
|
poll: 10
|
|
|
|
- name: Display build result
|
|
ansible.builtin.debug:
|
|
msg: |
|
|
Build result:
|
|
- Image: {{ registry_url }}/{{ image_name }}:{{ image_tag }}
|
|
- Return code: {{ build_result.rc | default('unknown') }}
|
|
- Changed: {{ build_result.changed | default(false) }}
|
|
- Failed: {{ build_result.failed | default(false) }}
|
|
when: build_result is defined
|
|
|
|
- name: Display build output on failure
|
|
ansible.builtin.debug:
|
|
msg: |
|
|
Build failed! Output:
|
|
{{ build_result.stdout_lines | default([]) | join('\n') }}
|
|
|
|
Error output:
|
|
{{ build_result.stderr_lines | default([]) | join('\n') }}
|
|
when:
|
|
- build_result is defined
|
|
- build_result.rc | default(0) != 0
|
|
|
|
- name: Verify image exists locally
|
|
ansible.builtin.command: |
|
|
docker image inspect {{ registry_url }}/{{ image_name }}:{{ image_tag }}
|
|
register: image_check
|
|
changed_when: false
|
|
failed_when: false
|
|
|
|
- name: Verify entrypoint script in built image
|
|
shell: |
|
|
CONTAINER_ID=$(docker create {{ registry_url }}/{{ image_name }}:{{ image_tag }} 2>/dev/null) && \
|
|
if docker cp $CONTAINER_ID:/usr/local/bin/entrypoint.sh /tmp/entrypoint_verify.sh 2>&1; then \
|
|
echo "FILE_EXISTS"; \
|
|
ls -la /tmp/entrypoint_verify.sh; \
|
|
head -3 /tmp/entrypoint_verify.sh; \
|
|
file /tmp/entrypoint_verify.sh; \
|
|
rm -f /tmp/entrypoint_verify.sh; \
|
|
else \
|
|
echo "FILE_NOT_FOUND"; \
|
|
fi && \
|
|
docker rm $CONTAINER_ID >/dev/null 2>&1 || true
|
|
register: image_entrypoint_check
|
|
changed_when: false
|
|
failed_when: false
|
|
|
|
- name: Display entrypoint verification result
|
|
debug:
|
|
msg: |
|
|
==========================================
|
|
Entrypoint Script Verification in Built Image
|
|
==========================================
|
|
Image: {{ registry_url }}/{{ image_name }}:{{ image_tag }}
|
|
|
|
Verification Result:
|
|
{{ image_entrypoint_check.stdout | default('Check failed') }}
|
|
|
|
{% if 'FILE_NOT_FOUND' in image_entrypoint_check.stdout %}
|
|
⚠️ CRITICAL: Entrypoint script NOT FOUND in built image!
|
|
|
|
This means the Docker build did not copy the entrypoint script.
|
|
Possible causes:
|
|
1. The COPY command in Dockerfile.production failed silently
|
|
2. The docker/entrypoint.sh file was not in the build context
|
|
3. There was an issue with the multi-stage build
|
|
|
|
Please check:
|
|
1. Build logs above for COPY errors
|
|
2. Verify docker/entrypoint.sh exists: ls -la {{ build_repo_path }}/docker/entrypoint.sh
|
|
3. Rebuild with verbose output to see COPY step
|
|
{% elif 'FILE_EXISTS' in image_entrypoint_check.stdout %}
|
|
✅ Entrypoint script found in built image
|
|
{% endif %}
|
|
==========================================
|
|
|
|
- name: Display image information
|
|
ansible.builtin.debug:
|
|
msg: |
|
|
✅ Image built and pushed successfully!
|
|
|
|
Registry: {{ registry_url }}
|
|
Image: {{ image_name }}:{{ image_tag }}
|
|
Local: {{ 'Available' if image_check.rc == 0 else 'Not found locally' }}
|
|
|
|
Next steps:
|
|
1. Run setup-infrastructure.yml to deploy the application stack
|
|
2. Or manually deploy using docker-compose
|
|
when: image_check.rc == 0
|
|
|
|
- name: Warn if image not found locally
|
|
ansible.builtin.debug:
|
|
msg: |
|
|
⚠️ Image was pushed but not found locally.
|
|
This is normal if the image was pushed to a remote registry.
|
|
|
|
Verify the image exists in the registry:
|
|
curl -u {{ registry_username }}:{{ registry_password }} http://{{ registry_url }}/v2/{{ image_name }}/tags/list
|
|
when: image_check.rc != 0
|
|
|