feat: Enhance website deployment and build process

- Added rsync installation in setup-env action for file synchronization.
- Excluded playbook_websites.yml from ansible-apply-playbook workflow.
- Created a new workflow for building and releasing the Hugo website.
- Added submodule for Hugo theme in .gitmodules.
- Updated host_vars for production to include websites configuration.
- Introduced playbook_websites.yml for managing static files deployment.
- Implemented hugo_build script for building and archiving Hugo sites.
- Updated inventory to include a new web group for website deployment.
- Refactored Terraform configuration to support new website structure.
- Removed deprecated WordPress Terraform configuration.
- Added .gitignore files for websites and specific Hugo site.
- Created archetype and configuration files for the Hugo site.
- Added static assets including CV and images for the Hugo site.
- Integrated Hugo profile theme as a submodule for the website.
This commit is contained in:
2025-04-27 14:03:56 +02:00
parent 2fbe4dd1cc
commit 4e5346e2e0
24 changed files with 850 additions and 258 deletions
+1 -1
View File
@@ -22,4 +22,4 @@ runs:
shell: bash
run: |
sudo apt-get update
sudo apt-get install -y python3-pip
sudo apt-get install -y python3-pip rsync
@@ -5,6 +5,7 @@ on:
- main
paths:
- ansible/playbook_*.yml
- '!ansible/playbook_websites.yml'
jobs:
ansible-apply:
runs-on: ubuntu-latest
+54
View File
@@ -0,0 +1,54 @@
name: Build and Release Hugo Website
on:
push:
branches:
- main
paths:
- 'ansible/playbook_websites.yml'
- 'websites/**'
jobs:
build:
name: Build Hugo Website
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v3
with:
submodules: true
fetch-depth: 0
- name: Setup Hugo
uses: peaceiris/actions-hugo@v2
with:
hugo-version: 'latest'
- name: Setup environment
id: setup_env
uses: ./.github/actions/setup-env
- name: Setup Ansible environment
uses: ./.github/actions/setup-ansible-env
with:
project-id: ${{ secrets.BW_PROJECT_ID }}
organization-id: ${{ secrets.BW_ORGANIZATION_ID }}
access-token: ${{ secrets.BW_ACCESS_TOKEN }}
ansible-user: ${{ secrets.ANSIBLE_USER }}
trusted-hosts: ${{ secrets.TRUSTED_HOSTS }}
- name: Run hugo_build command
run: bin/hugo_build.sh
- name: Run the playbook to deploy websites
env:
BW_PROJECT_ID: ${{ secrets.BW_PROJECT_ID }}
BW_ORGANIZATION_ID: ${{ secrets.BW_ORGANIZATION_ID }}
BW_ACCESS_TOKEN: ${{ secrets.BW_ACCESS_TOKEN }}
ANSIBLE_USER: ${{ secrets.ANSIBLE_USER }}
SSH_AUTH_SOCK: /tmp/ssh_agent.sock
CHANGED_FILES: ${{ steps.setup_env.outputs.CHANGED_FILES }}
run: |
cd ansible
sectool -f ../sectool.json exec ansible-playbook -u $ANSIBLE_USER playbook_websites.yml
+3
View File
@@ -0,0 +1,3 @@
[submodule "websites/themes/hugo-profile"]
path = websites/themes/hugo-profile
url = https://github.com/gurusabarish/hugo-profile.git
@@ -163,3 +163,7 @@ duo_api_hostname: api-7cda1654.duosecurity.com
# Apps
persistent_data: "/mnt/microk8s_persistent"
database_path: "/var/lib"
# Websites
websites:
- "alexpires.me"
+50
View File
@@ -0,0 +1,50 @@
---
- name: Websites (Static Files) Playbook (K8s)
hosts: web
gather_facts: true
# Specific tasks for this playbook
tasks:
- name: Set required facts
ansible.builtin.set_fact:
web_home: "{{ persistent_data | default('/var/lib') }}/web"
web_user_id: 1000
web_group_id: 1000
tags: always
- name: Create required web data folder
become: true
ansible.builtin.file:
state: directory
path: "{{ web_home }}"
mode: "0750"
owner: "{{ web_user_id }}"
group: "{{ web_group_id }}"
tags:
- tasks
- tasks::folders
- name: Create required web data folder
become: true
ansible.builtin.file:
state: directory
path: "{{ web_home }}/{{ item }}"
mode: "0750"
owner: "{{ web_user_id }}"
group: "{{ web_group_id }}"
with_items:
- "{{ websites | default([]) }}"
tags:
- tasks
- tasks::folders
- name: Synchronize websites
become: true
ansible.posix.synchronize:
mode: push
src: "{{ lookup('env', 'GITHUB_WORKSPACE') + '/websites/releases/'+ item }}/"
dest: "{{ web_home }}/{{ item }}"
delete: yes
recursive: yes
with_items:
- "{{ websites | default([]) }}"
Executable
+34
View File
@@ -0,0 +1,34 @@
#!/bin/bash
# This script is used to build a Hugo site and prepare it for deployment.
GITHUB_WORKSPACE=${GITHUB_WORKSPACE:-$(pwd)}
command -v hugo >/dev/null 2>&1 || { echo >&2 "Hugo is not installed. Please install Hugo to use this script."; exit 1; }
mkdir -p "$GITHUB_WORKSPACE/websites/releases"
for dir in $(find "$GITHUB_WORKSPACE/websites" -maxdepth 1 -type d | grep -vE '^\.$'); do
if [ "$dir" == "$GITHUB_WORKSPACE/websites" ]; then
echo "Skipping $dir"
continue
fi
if [ "$dir" == "$GITHUB_WORKSPACE/websites/themes" ]; then
echo "Skipping $dir"
continue
fi
if [ "$dir" == "$GITHUB_WORKSPACE/websites/releases" ]; then
echo "Skipping $dir"
continue
fi
if [ -d "$dir" ]; then
echo "Building Hugo site in $dir"
cd "$dir" || exit 1
hugo --minify --themesDir $GITHUB_WORKSPACE/websites/themes --destination $GITHUB_WORKSPACE/websites/releases/$(basename "$dir") --baseURL "https://$(basename "$dir").example.com"
if [ $? -ne 0 ]; then
echo "Hugo build failed in $dir"
exit 1
fi
echo "Hugo site built successfully in $dir"
else
echo "$dir is not a directory"
fi
done
+3 -3
View File
@@ -10,9 +10,6 @@ prod-01.alexpires.me
[gitea]
prod-01.alexpires.me
[wordpress]
prod-01.alexpires.me
[nextcloud]
prod-01.alexpires.me
@@ -33,3 +30,6 @@ prod-01.alexpires.me
[mail]
prod-01.alexpires.me
[web]
prod-01.alexpires.me
+12 -11
View File
@@ -1,25 +1,26 @@
locals {
# general
domain_name = "alexpires.me"
# storage
persistent_folder = "/mnt/microk8s_persistent"
database_folder = "/var/lib"
# apps
wordpress_version = "6.7.2"
wordpress_fqdns = ["blog.${local.domain_name}", "www.${local.domain_name}", "${local.domain_name}"]
wordpress_sql_backup_cron_schedule = "0 2 * * *"
wordpress_files_backup_cron_schedule = "10 2 * * *"
# domains
primary_domain = "alexpires.me"
websites = {
"alexpires.me" = {
domain_name = "alexpires.me"
fqdn = ["blog.alexpires.me", "www.alexpires.me", "alexpires.me"]
}
}
gitea_version = "1.23.4"
gitea_fqdn = "code.${local.domain_name}"
gitea_fqdn = "code.${local.primary_domain}"
gitea_sql_backup_cron_schedule = "20 2 * * *"
gitea_files_backup_cron_schedule = "30 2 * * *"
nextcloud_version = "30.0.0-apache"
nextcloud_fqdn = "cloud.${local.domain_name}"
nextcloud_fqdn = "cloud.${local.primary_domain}"
nextcloud_sql_backup_cron_schedule = "40 2 * * *"
nextcloud_files_backup_cron_schedule = "50 2 * * *"
@@ -34,7 +35,7 @@ locals {
m3uproxy_version = "latest"
wireguard_version = "1.0.20210914-r4-ls54"
squid_version = "6.1-23.10_edge"
m3uproxy_fqdn = "tv.${local.domain_name}"
m3uproxy_fqdn = "tv.${local.primary_domain}"
geoip_image = "maxmindinc/geoipupdate"
geoip_version = "latest"
@@ -47,7 +48,7 @@ locals {
# rustdesk
rustdesk_version = "latest"
rustdesk_fqdn = "rustdesk.${local.domain_name}"
rustdesk_fqdn = "rustdesk.${local.primary_domain}"
# backup
local_backup_retention_days = 2
+4 -4
View File
@@ -25,10 +25,10 @@ locals {
# Email configuration
SMTP_HOST = local.scaleway_smtp_host
SMTP_PORT = 465
MY_PRIMARY_DOMAIN = local.domain_name
MY_PRIMARY_DOMAIN = local.primary_domain
MY_DESTINATION = join(",", [for account in local.email_accounts : account.domain])
MY_NETWORKS = "127.0.0.0/8"
MAILNAME = "smtp.${local.domain_name}"
MAILNAME = "smtp.${local.primary_domain}"
DOVECOT_AUTH_ADDRESS = "dovecot-auth.mail.svc.cluster.local"
DOVECOT_AUTH_PORT = 31000
DOVECOT_LMTP_ADDRESS = "dovecot-lmtp.mail.svc.cluster.local"
@@ -201,8 +201,8 @@ resource "kubernetes_manifest" "mail_certificate" {
name = "letsencrypt-prod"
kind = "ClusterIssuer"
}
commonName = "imap.${local.domain_name}"
dnsNames = ["imap.${local.domain_name}", "smtp.${local.domain_name}"]
commonName = "imap.${local.primary_domain}"
dnsNames = flatten([for account in local.email_accounts : ["imap.${account.domain}", "smtp.${account.domain}"]])
}
}
}
@@ -0,0 +1,35 @@
server {
listen 8080;
server_name localhost;
gzip on;
gzip_comp_level 9;
gzip_types
text/html
text/plain
text/css
text/js
text/xml
text/javascript
application/javascript
application/json
application/xml
application/rss+xml
image/svg+xml;
root /tmp/site;
location / {
index index.html index.htm;
}
location ~* ^/([^/]+) {
index index.html index.htm;
error_page 404 = @error;
}
error_page 404 /404.html;
location @error {
try_files /$1/404.html /404.html =404;
}
}
@@ -0,0 +1,34 @@
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /tmp/nginx.pid;
events {
worker_connections 1024;
}
http {
proxy_temp_path /tmp/proxy_temp;
client_body_temp_path /tmp/client_temp;
fastcgi_temp_path /tmp/fastcgi_temp;
uwsgi_temp_path /tmp/uwsgi_temp;
scgi_temp_path /tmp/scgi_temp;
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
keepalive_timeout 65;
include /etc/nginx/conf.d/*.conf;
}
+201
View File
@@ -0,0 +1,201 @@
locals {
websites_path = "${local.persistent_folder}/web"
}
resource "kubernetes_namespace" "website" {
metadata {
annotations = {
name = "website"
}
labels = {
name = "website"
}
name = "website"
}
}
resource "kubernetes_service_account" "website_service_account" {
metadata {
name = "website-app-sa"
namespace = kubernetes_namespace.website.metadata[0].name
}
automount_service_account_token = false
}
resource "kubernetes_service" "website" {
for_each = local.websites
metadata {
name = format("website-%s", replace(each.key, ".", "-"))
namespace = kubernetes_namespace.website.metadata[0].name
}
spec {
port {
port = 80
target_port = "http"
}
selector = {
app = format("website-%s", replace(each.key, ".", "-"))
}
cluster_ip = "None"
}
}
resource "kubernetes_config_map" "nginx_config" {
metadata {
name = "nginx-config"
namespace = kubernetes_namespace.website.metadata[0].name
}
data = {
"nginx.conf" = file("${path.module}/resources/nginx/nginx.conf")
"default.conf" = file("${path.module}/resources/nginx/conf.d/default.conf")
}
}
resource "kubernetes_ingress_v1" "website_ingress" {
for_each = local.websites
metadata {
name = "website-ingress"
namespace = kubernetes_namespace.website.metadata[0].name
annotations = {
"kubernetes.io/ingress.class" = "public"
"cert-manager.io/cluster-issuer" = "letsencrypt-prod"
}
}
spec {
tls {
hosts = each.value.fqdn
secret_name = format("website-%s-tls", replace(each.key, ".", "-"))
}
dynamic "rule" {
for_each = each.value.fqdn
content {
host = rule.value
http {
path {
backend {
service {
name = format("website-%s", replace(each.key, ".", "-"))
port {
number = 80
}
}
}
}
}
}
}
}
}
resource "kubernetes_deployment" "website" {
for_each = local.websites
metadata {
name = "website-app"
namespace = kubernetes_namespace.website.metadata[0].name
}
spec {
selector {
match_labels = {
app = format("website-%s", replace(each.key, ".", "-"))
}
}
strategy {
type = "RollingUpdate"
}
template {
metadata {
labels = {
app = format("website-%s", replace(each.key, ".", "-"))
}
}
spec {
service_account_name = kubernetes_service_account.website_service_account.metadata[0].name
security_context {
fs_group = 1000
run_as_user = 1000
}
container {
name = "website"
image = "nginx:stable-alpine"
image_pull_policy = "Always"
resources {
requests = {
memory = "100Mi"
}
limits = {
memory = "500Mi"
}
}
readiness_probe {
http_get {
path = "/"
port = 8080
}
initial_delay_seconds = 5
period_seconds = 10
}
liveness_probe {
http_get {
path = "/"
port = 8080
}
initial_delay_seconds = 5
period_seconds = 10
}
security_context {
allow_privilege_escalation = false
}
port {
container_port = 8080
name = "http"
}
volume_mount {
name = "nginx-config"
mount_path = "/etc/nginx/nginx.conf"
sub_path = "nginx.conf"
read_only = true
}
volume_mount {
name = "nginx-config"
mount_path = "/etc/nginx/conf.d/default.conf"
sub_path = "default.conf"
read_only = true
}
volume_mount {
name = "website"
mount_path = "/tmp/site"
read_only = true
}
}
volume {
name = "nginx-config"
config_map {
name = kubernetes_config_map.nginx_config.metadata[0].name
}
}
volume {
name = "website"
host_path {
path = format("%s/%s", local.websites_path, each.key)
}
}
}
}
}
}
-239
View File
@@ -1,239 +0,0 @@
locals {
wordpress_path = "${local.persistent_folder}/wordpress"
wordpress_db_path = "${local.database_folder}/mysql.wordpress"
wordpress_backup_path = "${local.persistent_folder}/backup.wordpress"
}
resource "kubernetes_namespace" "wordpress" {
metadata {
annotations = {
name = "wordpress"
}
labels = {
name = "wordpress"
}
name = "wordpress"
}
}
#
# Setup MariaDB database
#
module "wordpress_database" {
source = "./modules/k8s_mariadb"
namespace = kubernetes_namespace.wordpress.metadata[0].name
app_name = "wordpress"
app_password = var.wordpress_mysql_password
root_password = var.mysql_root_password
retention_days = local.local_backup_retention_days
cron_schedule = local.wordpress_sql_backup_cron_schedule
database_folder = local.wordpress_db_path
backup_folder = local.wordpress_backup_path
}
#
# Setup rsync backup job to scaleway bucket
#
module "wordpress_backup" {
source = "./modules/scw_backup_job"
namespace = kubernetes_namespace.wordpress.metadata[0].name
app_name = "wordpress"
scaleway_organization_id = var.scaleway_organization_id
scaleway_project_id = var.scaleway_organization_id
access_key = scaleway_iam_api_key.backup_app_api.access_key
secret_key = scaleway_iam_api_key.backup_app_api.secret_key
bucket_name = scaleway_object_bucket.backup.name
retention_days = local.local_backup_retention_days
cron_schedule = local.wordpress_files_backup_cron_schedule
source_folder = local.wordpress_path
target_folder = local.wordpress_backup_path
}
#
# Gitea service, ingress, deployment and file backup job
#
resource "kubernetes_service_account" "wordpress_service_account" {
metadata {
name = "wordpress-app-sa"
namespace = kubernetes_namespace.wordpress.metadata[0].name
}
automount_service_account_token = false
}
resource "kubernetes_service" "wordpress" {
metadata {
name = "http"
namespace = kubernetes_namespace.wordpress.metadata[0].name
}
spec {
port {
port = 80
target_port = "http"
}
selector = {
app = "wordpress"
}
cluster_ip = "None"
}
}
resource "kubernetes_ingress_v1" "wordpress_ingress" {
metadata {
name = "wordpress-ingress"
namespace = kubernetes_namespace.wordpress.metadata[0].name
annotations = {
"kubernetes.io/ingress.class" = "public"
"cert-manager.io/cluster-issuer" = "letsencrypt-prod"
}
}
spec {
tls {
hosts = local.wordpress_fqdns
secret_name = "wordpress-le-tls"
}
dynamic "rule" {
for_each = local.wordpress_fqdns
content {
host = rule.value
http {
path {
backend {
service {
name = "http"
port {
number = 80
}
}
}
}
}
}
}
}
}
resource "kubernetes_deployment" "wordpress" {
depends_on = [module.wordpress_database]
metadata {
name = "wordpress-app"
namespace = kubernetes_namespace.wordpress.metadata[0].name
}
spec {
selector {
match_labels = {
app = "wordpress"
}
}
strategy {
type = "RollingUpdate"
}
template {
metadata {
labels = {
app = "wordpress"
}
}
spec {
service_account_name = kubernetes_service_account.wordpress_service_account.metadata[0].name
container {
name = "wordpress"
image = "wordpress:${local.wordpress_version}"
image_pull_policy = "IfNotPresent"
resources {
requests = {
memory = "100Mi"
}
limits = {
memory = "500Mi"
}
}
# readiness_probe {
# http_get {
# path = "/?nocache"
# port = "http"
# }
# initial_delay_seconds = 20
# period_seconds = 10
# success_threshold = 1
# failure_threshold = 5
# }
# liveness_probe {
# tcp_socket {
# port = "http"
# }
# initial_delay_seconds = 30
# period_seconds = 60
# success_threshold = 1
# failure_threshold = 10
# }
security_context {
allow_privilege_escalation = false
}
env {
name = "WORDPRESS_DB_NAME"
value = "wordpress"
}
env {
name = "WORDPRESS_DB_HOST"
value = "${module.wordpress_database.host}:${module.wordpress_database.port}"
}
env {
name = "WORDPRESS_DB_USER"
value_from {
secret_key_ref {
key = "username"
name = module.wordpress_database.app_crendentials_name
}
}
}
env {
name = "WORDPRESS_DB_PASSWORD"
value_from {
secret_key_ref {
key = "password"
name = module.wordpress_database.app_crendentials_name
}
}
}
port {
container_port = 80
name = "http"
}
volume_mount {
mount_path = "/var/www/html"
name = "persistent-storage"
}
}
volume {
name = "persistent-storage"
host_path {
path = local.wordpress_path
}
}
}
}
}
}
+1
View File
@@ -0,0 +1 @@
releases
+39
View File
@@ -0,0 +1,39 @@
# Created by https://www.toptal.com/developers/gitignore/api/visualstudiocode,hugo
# Edit at https://www.toptal.com/developers/gitignore?templates=visualstudiocode,hugo
### Hugo ###
# Generated files by hugo
/public/
/resources/_gen/
/assets/jsconfig.json
hugo_stats.json
# Executable may be added to repository
hugo.exe
hugo.darwin
hugo.linux
# Temporary lock file while building
/.hugo_build.lock
### VisualStudioCode ###
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/*.code-snippets
# Local History for Visual Studio Code
.history/
# Built Visual Studio Code Extensions
*.vsix
### VisualStudioCode Patch ###
# Ignore all local history of files
.history
.ionide
.~lock.*.odt#
# End of https://www.toptal.com/developers/gitignore/api/visualstudiocode,hugo
@@ -0,0 +1,5 @@
---
date: '{{ .Date }}'
draft: true
title: '{{ replace .File.ContentBaseName "-" " " | title }}'
---
+368
View File
@@ -0,0 +1,368 @@
baseURL: https://alexpires.me/
languageCode: en-us
title: Alexandre Pires
theme: hugo-profile
outputs:
home:
- "HTML"
- "RSS"
- "JSON"
page:
- "HTML"
- "RSS"
enableRobotsTXT: true
#services:
#googleAnalytics:
#id: G-MEASUREMENT_ID
#disqus:
#shortname: your-disqus-shortname
pagination:
pagerSize: 3
markup:
goldmark:
renderer:
unsafe: true
Menus:
# main:
# - identifier: blog
# name: Blog
# title: Blog posts
# url: /blogs
# weight: 1
# - identifier: gallery
# name: Gallery
# title: Blog posts
# url: /gallery
# weight: 2
#Dropdown menu
# - identifier: dropdown
# title: Example dropdown menu
# name: Dropdown
# weight: 3
# - identifier: dropdown1
# title: example dropdown 1
# name: example 1
# url: /#
# parent: dropdown
# weight: 1
# - identifier: dropdown2
# title: example dropdown 2
# name: example 2
# url: /#
# parent: dropdown
# weight: 2
params:
title: "Alexandre Pires - Software/Systems Engineer"
description: Text about my cool site
# staticPath: "" # The path to serve the static files from
favicon: "/fav.png"
# Whether to serve bootstrap css and js files from CDN or not. Can be set to true, "css" or "js" to choose between
# serving both, only the css, or only the js files through the CDN. Any other value will make so that CDN is not used.
# Note the lack of "" in true, it should be of boolean type.
useBootstrapCDN: false
# If you want to load dynamically responsive images from Cloudinary
# This requires your images to be uploaded + hosted on Cloudinary
# Uncomment and change YOUR_CLOUD_NAME to the Cloud Name in your Cloudinary console
# cloudinary_cloud_name: "YOUR_CLOUD_NAME"
# Whether to add mathjax support on all pages. Alternatively, you can opt-in per page by adding `mathjax: true` in the frontmatter.
mathjax: false
# Whether the fade animations on the home page will be enabled
animate: true
theme:
disableThemeToggle: true
defaultTheme: "dark"
font:
fontSize: 1rem # default: 1rem
fontWeight: 400 # default: 400
lineHeight: 1.5 # default: 1.5
textAlign: left # default: left
# color preference
# When using hex codes for colors, quotations must be used along with the # sign
# color:
# textColor: "#343a40"
# secondaryTextColor: "#6c757d"
# textLinkColor: "#007bff"
# backgroundColor: "#eaedf0"
# secondaryBackgroundColor: "#64ffda1a"
# primaryColor: "#007bff"
# secondaryColor: "#f8f9fa"
# darkmode:
# textColor: "#e4e6eb"
# secondaryTextColor: "#b0b3b8"
# textLinkColor: "#ffffff"
# backgroundColor: "#18191a"
# secondaryBackgroundColor: "#212529"
# primaryColor: "#ffffff"
# secondaryColor: "#212529"
# If you want to customize the menu, you can change it here
navbar:
align: ms-auto # Left: ms-auto | center: mx-auto | right: me-auto | Default: ms-auto
# brandLogo: "/logo.png" # Logo for the brand | default is the favicon variable
# showBrandLogo: false # Show brand logo in nav bar | default is true
brandName: "Alexandre Pires" # Brand name for the brand | default is the title variable
disableSearch: true
# searchPlaceholder: "Search"
stickyNavBar:
enable: true
showOnScrollUp: true
enableSeparator: false
menus:
disableAbout: false
disableExperience: false
disableEducation: false
disableProjects: false
disableAchievements: false
disableContact: false
# Hero
hero:
enable: true
intro: "Welcome, my name is"
title: "Alexandre Pires."
subtitle: "Software/Systems Engineer"
content: "A passionate IT professional with 10+ years of experience building tools on open-source tech, delivering solutions that drive business success."
image: /images/hero.png
bottomImage:
enable: true
# roundImage: true # Make hero image circular | default false
button:
enable: true
name: "Resume"
url: "cv.pdf"
download: true
newPage: false
socialLinks:
fontAwesomeIcons:
- icon: fab fa-github
url: https://github.com/a13labs/
- icon: fab fa-linkedin-in
url: https://www.linkedin.com/in/carlos-pires-b2597886/
# About
about:
enable: true
title: "About Me"
image: "/images/me.png"
content: |-
Over 10 years of experience in developing software and tools on top of open-source technologies, managing server infrastructures across architectures for high availability and performance, delivering business-driven solutions, and leading systems, projects, and teams across diverse IT environments.
skills:
enable: true
title: "Here are a few skills I've been improving over the years:"
items:
- "Software development"
- "Cloud platforms"
- "Automation"
- "Infrastructure as Code"
- "Virtualization"
- "Project management"
- "Team leading and mentoring"
# Experience
experience:
enable: true
# title: "Custom Name"
items:
- job: "Cloud Native/Platform Engineer"
company: "KBC"
companyUrl: "https://kbc.be"
date: "Jul 2021 - present"
info:
enable: false
content: "AWS engineer focused on building a Kubernetes-based platform. Part of a delivery-to-business team, responsible for designing, developing, and driving adoption of an EKS platform for business teams, with a strong focus on architectural design and building CI/CD solutions using GitHub Workflows."
- job: "DevOps/Linux System Engineer"
company: "Siemens"
companyUrl: "https://siemens.com"
date: "Jan 2021 - Jun 2021"
info:
enable: false
content: |
Hired as a Linux System Engineer to develop and serve as a CI/CD Engineer for a new Azure DevOps-based Linux solution supporting the company's internal clients. Main responsibilities included using technologies like Docker, Ansible, Molecule, and Podman to design and build the core platform for clients to build, test, and deploy their software.
- job: "DevOps/Backend Lead Developer"
company: "MovAI"
companyUrl: "https://mov.ai"
date: "Set 2019 - Dec 2020"
info:
enable: false
content: |
As the Backend/DevOps Lead, I was responsible for designing, implementing, and maintaining the CI/CD pipeline infrastructure. I worked closely with the Project Manager and R&D Manager to establish proper procedures and methodologies for a software development company, participated in the QA process, and led the design and implementation of the company's product deployment strategy. As the most senior developer, I played a key role in mentoring new team members, introducing best practices and design patterns, and helping to elevate the technical skills of the existing development team.
- job: "Linux System Engineer / DevOps Engineer"
company: "ALPEGA Group"
companyUrl: "https://www.alpegagroup.com/"
date: "Set 2018 - Aug 2019"
info:
enable: false
content: |
Hired full-time as a Systems and DevOps Engineer to operate and enhance infrastructure, implement a new monitoring solution and deployment pipeline in AWS, enabling the adoption of DevOps practices and CI/CD. Played a key technical role in the company's 'lift-and-shift' migration project to AWS."
- job: "Linux System Engineer / Senior IT Manager"
company: "HBD"
companyUrl: "https:///hbd.com"
date: "Mar 2013 - Aug 2018"
info:
enable: false
content: |
HBD is a private South African investment company focused on the sustainable and responsible development of Príncipe Island. With around 500 employees, HBD invests in social, agricultural, and infrastructure development, aiming to establish eco-resorts, including 5-star properties.
Initially hired as a consultant, then brought on full-time to build the IT department from the ground up and restructure the companys network and IT technology portfolio.
# Education
education:
enable: true
# title: "Custom Name"
index: false
items:
- title: "Bachelor of Science Robotics Engineering"
school:
name: "International University of Applied Sciences, Germany"
url: "https://iu.org"
date: "2021 - present"
GPA: ""
content:
- title: "Bachelor of Science Computer Science Engineering"
school:
name: "Universidade Aberta, Portugal"
url: "https://portal.uab.pt/"
date: "2016 - 2023"
GPA: "4 out of 5.0"
content: |-
I Publiced one papers in the University Journal on parallel computing.
- [Proposal for an optimized variant of A* Algorithm for multi-core systems ](https://journals.uab.pt/index.php/rcc/article/view/297)
- title: "High School"
school:
name: "Escola secundária de Anadia"
url: "https://aeanadia.pt/"
GPA: "3 out of 5.0"
# Achievements
achievements:
enable: false
# title: "Custom Name"
items:
- title: Google kickstart runner
content: I solved all problems with optimal solution.
url: https://example.com
image: /images/achievement.jpg
# projects
projects:
enable: false
# title: "Custom Name"
items:
- title: Hugo Profile
content: A highly customizable and mobile first Hugo template for personal portfolio and blog.
image: /images/projects/profile.png
featured:
name: Demo
link: https://hugo-profile.netlify.app
badges:
- "Hugo"
- "Bootstrap"
- "Javascript"
links:
- icon: fa fa-envelope
url: mailto:?subject=Hugo%20Profile%20Template&body=Check%20it%20out:%20https%3a%2f%2fhugo-profile.netlify.app%2fblog%2fmarkdown-syntax%2f
- icon: fab fa-github
url: https://github.com/gurusabarish/hugo-profile
- icon: fab fa-twitter
url: https://twitter.com/intent/tweet?text=Check+it+out:&url=https%3A%2F%2Fgithub.com%2Fgurusabarish%2Fhugo-profile
- title: Image Converter
content: A web app to convert image to pdf, png to jpg, png to jpg and png to webp without database using django.
image: /images/projects/converter.jpg
featured:
name: Demo
link: https://django-converter.herokuapp.com
badges:
- "Django"
- "Bootstrap"
links:
- icon: fab fa-github
url: https://github.com/gurusabarish/converter
- title: Hugo Profile V2
content: A clean and simple Hugo template for personal portfolio and blog.
image: /images/projects/profile2.jpg
featured:
name: Demo V2
link: https://hugo-profile-2.netlify.app
badges:
- "Hugo"
- "Bootstrap"
- "Javascript"
links:
- icon: fab fa-github
url: https://github.com/gurusabarish/HugoProfileV2
#Contact
contact:
enable: true
# title: "Custom Name"
content: My inbox is always open. Whether you have a question or just want to say hi, Ill try my best to get back to you!
btnName: Mail me
btnLink: mailto:c.alexandre.pires@alexpires.me
# formspree:
# enable: true # `contact.email` value will be ignored
# formId: abcdefgh # Take it from your form's endpoint, like 'https://formspree.io/f/abcdefgh'
# emailCaption: "Enter your email address"
# messageCaption: "Enter your message here"
# messageRows: 5
footer:
recentPosts:
path: "blogs"
count: 3
title: Recent Posts
enable: false
disableFeaturedImage: false
socialNetworks:
github: https://github.com/a13labs/
linkedin: https://www.linkedin.com/in/carlos-pires-b2597886/
# List pages like blogs and posts
listPages:
disableFeaturedImage: false
# Single pages like blog and post
singlePages:
socialShare: true
readTime:
enable: true
content: "min read"
scrollprogress:
enable: true
tags:
openInNewTab: true
# For translations
terms:
read: "Read"
toc: "Table Of Contents"
copyright: "All rights reserved"
pageNotFound: "Page not found"
emailText: "Check out this site"
datesFormat:
article: "Jan 2, 2006"
articleList: "Jan 2, 2006"
articleRecent: "Jan 2, 2006"
#customScripts: -| # You can add custom scripts which will be added before </body> tag
# <script type="text/javascript"><!-- any script here --></script>
Binary file not shown.
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 319 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 KiB