diff --git a/ansible/.ansible-lint b/ansible/.ansible-lint
new file mode 100644
index 0000000..0c31185
--- /dev/null
+++ b/ansible/.ansible-lint
@@ -0,0 +1,2 @@
+skip_list:
+  - '403' # Package installs should not use latest.
diff --git a/ansible/ansible.cfg b/ansible/ansible.cfg
new file mode 100644
index 0000000..ae61bc8
--- /dev/null
+++ b/ansible/ansible.cfg
@@ -0,0 +1,5 @@
+[defaults]
+roles_path = roles/
+
+[connection]
+pipelining = True
\ No newline at end of file
diff --git a/ansible/packer.yml b/ansible/packer.yml
new file mode 100644
index 0000000..463c770
--- /dev/null
+++ b/ansible/packer.yml
@@ -0,0 +1,14 @@
+---
+- name: Configure template
+  hosts: default
+  become: true
+  roles:
+    - auditd
+    - fail2ban
+    - journald
+    - sshd
+    - timesyncd
+  post_tasks:
+    - name: Clean cloud-init
+      ansible.builtin.command: "cloud-init clean"
+      changed_when: false
diff --git a/ansible/roles/auditd/files/custom.rules b/ansible/roles/auditd/files/custom.rules
new file mode 100755
index 0000000..efdd0db
--- /dev/null
+++ b/ansible/roles/auditd/files/custom.rules
@@ -0,0 +1,32 @@
+-w /sbin/insmod -p x -k insmod_execute
+-w /sbin/modprobe -p x -k modprobe_execute
+-w /sbin/rmmod -p x -k rmmod_execute
+-w /bin/kmod -p x -k kmod_execute
+
+-w /etc/ -p wa -k etc_change
+-w /dev/shm/ -p wa -k share_memory_change
+
+-w /root/ -p wa -k root_home_change
+-w /etc/passwd -p wa -k passwd_change
+-w /etc/shadow -p rwa -k shadow_change
+-w /etc/group -p wa -k group_change
+-w /etc/security -k security_change
+-w /etc/audit/ -p rwa -k etc_audit_change
+-w /etc/sudoers -p wa -k sudoers_change
+-w /etc/sudoers.d -p wa -k sudoers_change
+
+-a exit,always -F arch=b64 -S mount -S umount2 -k partition_mount
+
+-a exit,always -F arch=b64 -S ioperm -S modify_ldt -k ioperm_modify_ldt
+
+-a exit,always -F arch=b64 -S get_kernel_syms -S ptrace -k get_kernel_syms
+
+-a exit,always -F arch=b64 -S unlink -S rmdir -S rename -k unlink_rmdir
+-a exit,always -F arch=b64 -S creat -S open -S openat -F exit=-EACCES -k creat_openat
+-a exit,always -F arch=b64 -S truncate -S ftruncate -F exit=-EACCES -k truncate
+
+-a exit,always -F arch=b64 -S init_module -S delete_module -k init_delete_module
+-a exit,always -F arch=b64 -S finit_module -k finit_module -k finit
+
+-e 2
+-f 2
\ No newline at end of file
diff --git a/ansible/roles/auditd/handlers/main.yml b/ansible/roles/auditd/handlers/main.yml
new file mode 100755
index 0000000..17a7bb5
--- /dev/null
+++ b/ansible/roles/auditd/handlers/main.yml
@@ -0,0 +1,10 @@
+---
+- name: Start auditd
+  ansible.builtin.systemd:
+    name: auditd
+    state: started
+
+- name: Enable auditd
+  ansible.builtin.systemd:
+    name: auditd
+    enabled: true
diff --git a/ansible/roles/auditd/tasks/main.yml b/ansible/roles/auditd/tasks/main.yml
new file mode 100755
index 0000000..4588752
--- /dev/null
+++ b/ansible/roles/auditd/tasks/main.yml
@@ -0,0 +1,14 @@
+---
+- name: Install audit
+  ansible.builtin.apt:
+    name: auditd
+    state: latest
+  notify:
+    - Start auditd
+    - Enable auditd
+
+- name: Add rules
+  ansible.builtin.copy:
+    src: 'custom.rules'
+    dest: '/etc/audit/rules.d/custom.rules'
+    mode: "0640"
diff --git a/ansible/roles/fail2ban/files/sshd.conf b/ansible/roles/fail2ban/files/sshd.conf
new file mode 100755
index 0000000..0730a9c
--- /dev/null
+++ b/ansible/roles/fail2ban/files/sshd.conf
@@ -0,0 +1,4 @@
+[sshd]
+enabled = true
+bantime = -1
+maxretry = 3
diff --git a/ansible/roles/fail2ban/handlers/main.yml b/ansible/roles/fail2ban/handlers/main.yml
new file mode 100755
index 0000000..094e747
--- /dev/null
+++ b/ansible/roles/fail2ban/handlers/main.yml
@@ -0,0 +1,5 @@
+---
+- name: Restart fail2ban
+  ansible.builtin.systemd:
+    name: fail2ban.service
+    state: restarted
diff --git a/ansible/roles/fail2ban/tasks/main.yml b/ansible/roles/fail2ban/tasks/main.yml
new file mode 100755
index 0000000..154e486
--- /dev/null
+++ b/ansible/roles/fail2ban/tasks/main.yml
@@ -0,0 +1,19 @@
+---
+- name: Install fail2ban
+  ansible.builtin.apt:
+    name: "fail2ban"
+    state: latest
+
+- name: Copy sshd.conf
+  ansible.builtin.copy:
+    src: 'sshd.conf'
+    dest: '/etc/fail2ban/jail.d/sshd.conf'
+    mode: "0640"
+  notify:
+    - Restart fail2ban
+
+- name: Start fail2ban.service
+  ansible.builtin.systemd:
+    state: started
+    name: fail2ban.service
+    enabled: true
diff --git a/ansible/roles/journald/files/retention-time.conf b/ansible/roles/journald/files/retention-time.conf
new file mode 100755
index 0000000..ae81718
--- /dev/null
+++ b/ansible/roles/journald/files/retention-time.conf
@@ -0,0 +1,2 @@
+[Journal]
+MaxRetentionSec=7day
diff --git a/ansible/roles/journald/handlers/main.yml b/ansible/roles/journald/handlers/main.yml
new file mode 100755
index 0000000..3fee2c9
--- /dev/null
+++ b/ansible/roles/journald/handlers/main.yml
@@ -0,0 +1,5 @@
+---
+- name: Restart journald
+  ansible.builtin.systemd:
+    name: systemd-journald.service
+    state: restarted
diff --git a/ansible/roles/journald/tasks/main.yml b/ansible/roles/journald/tasks/main.yml
new file mode 100755
index 0000000..45400e5
--- /dev/null
+++ b/ansible/roles/journald/tasks/main.yml
@@ -0,0 +1,14 @@
+---
+- name: Create /etc/systemd/journald.conf.d
+  ansible.builtin.file:
+    path: '/etc/systemd/journald.conf.d'
+    state: 'directory'
+    mode: "0750"
+
+- name: Copy retention-time.conf
+  ansible.builtin.copy:
+    src: 'retention-time.conf'
+    dest: '/etc/systemd/journald.conf.d'
+    mode: "0640"
+  notify:
+    - Restart journald
diff --git a/ansible/roles/sshd/files/crypto.conf b/ansible/roles/sshd/files/crypto.conf
new file mode 100644
index 0000000..546ecdf
--- /dev/null
+++ b/ansible/roles/sshd/files/crypto.conf
@@ -0,0 +1,17 @@
+# Restrict key exchange, cipher, and MAC algorithms, as per sshaudit.com
+# hardening guide.
+KexAlgorithms sntrup761x25519-sha512@openssh.com,curve25519-sha256,curve25519-sha256@libssh.org,gss-curve25519-sha256-,diffie-hellman-group16-sha512,gss-group16-sha512-,diffie-hellman-group18-sha512,diffie-hellman-group-exchange-sha256
+
+Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr
+
+MACs hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,umac-128-etm@openssh.com
+
+HostKeyAlgorithms sk-ssh-ed25519-cert-v01@openssh.com,ssh-ed25519-cert-v01@openssh.com,rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-256-cert-v01@openssh.com,sk-ssh-ed25519@openssh.com,ssh-ed25519,rsa-sha2-512,rsa-sha2-256
+
+CASignatureAlgorithms sk-ssh-ed25519@openssh.com,ssh-ed25519,rsa-sha2-512,rsa-sha2-256
+
+GSSAPIKexAlgorithms gss-curve25519-sha256-,gss-group16-sha512-
+
+HostbasedAcceptedAlgorithms sk-ssh-ed25519-cert-v01@openssh.com,ssh-ed25519-cert-v01@openssh.com,sk-ssh-ed25519@openssh.com,ssh-ed25519,rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-512,rsa-sha2-256-cert-v01@openssh.com,rsa-sha2-256
+
+PubkeyAcceptedAlgorithms sk-ssh-ed25519-cert-v01@openssh.com,ssh-ed25519-cert-v01@openssh.com,sk-ssh-ed25519@openssh.com,ssh-ed25519,rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-512,rsa-sha2-256-cert-v01@openssh.com,rsa-sha2-256
diff --git a/ansible/roles/sshd/handlers/main.yml b/ansible/roles/sshd/handlers/main.yml
new file mode 100644
index 0000000..89aa775
--- /dev/null
+++ b/ansible/roles/sshd/handlers/main.yml
@@ -0,0 +1,5 @@
+- name: Restart SSH
+  ansible.builtin.service:
+    name: sshd
+    state: restarted
+    enabled: true
diff --git a/ansible/roles/sshd/tasks/main.yml b/ansible/roles/sshd/tasks/main.yml
new file mode 100644
index 0000000..13a0776
--- /dev/null
+++ b/ansible/roles/sshd/tasks/main.yml
@@ -0,0 +1,40 @@
+---
+- name: Disable Password Authentication
+  ansible.builtin.lineinfile:
+    dest: /etc/ssh/sshd_config
+    regexp: '^PasswordAuthentication'
+    line: "PasswordAuthentication no"
+    state: present
+    backup: true
+  notify:
+    - Restart SSH
+
+- name: Disable Root Login
+  ansible.builtin.lineinfile:
+    dest: /etc/ssh/sshd_config
+    regexp: '^PermitRootLogin'
+    line: "PermitRootLogin no"
+    state: present
+    backup: true
+  notify:
+    - Restart SSH
+
+- name: Restrict host key
+  ansible.builtin.lineinfile:
+    dest: /etc/ssh/sshd_config
+    regexp: '#HostKey /etc/ssh/ssh_host_ed25519_key'
+    line: "HostKey /etc/ssh/ssh_host_ed25519_key"
+    state: present
+    backup: true
+  notify:
+    - Restart SSH
+
+- name: Configure sshd
+  ansible.builtin.copy:
+    src: "crypto.conf"
+    dest: "/etc/ssh/sshd_config.d/"
+    owner: root
+    group: root
+    mode: "0640"
+  notify:
+    - Restart SSH
\ No newline at end of file
diff --git a/ansible/roles/timesyncd/tasks/main.yml b/ansible/roles/timesyncd/tasks/main.yml
new file mode 100755
index 0000000..51079fe
--- /dev/null
+++ b/ansible/roles/timesyncd/tasks/main.yml
@@ -0,0 +1,8 @@
+---
+- name: Set timezone to Europe/Brussels
+  community.general.timezone:
+    name: Europe/Brussels
+
+- name: Enable ntp
+  ansible.builtin.command: 'timedatectl set-ntp true'
+  changed_when: false
diff --git a/packer/http/meta-data b/packer/http/meta-data
new file mode 100644
index 0000000..e69de29
diff --git a/packer/http/user-data b/packer/http/user-data
new file mode 100644
index 0000000..e804715
--- /dev/null
+++ b/packer/http/user-data
@@ -0,0 +1,29 @@
+#cloud-config
+autoinstall:
+  version: 1
+  source:
+    id: ubuntu-server-minimal
+  identity:
+    hostname: ubuntu-2204-r4
+    password: "${password_crypt}"
+    username: "${user}"
+  user-data:
+    disable_root: true
+  refresh-installer:
+    update: true
+  packages:
+  - openssh-server
+  - qemu-guest-agent
+  - cloud-init
+  ssh:
+    allow-pw: false
+    install-server: true
+    authorized-keys:
+      - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILBPOnqmExTKt8OceAq9K4d/MkVu4WsU+sqiEV7KbtyH"
+  storage:
+    layout:
+      name: direct
+  kernel:
+    package: linux-image-kvm
+  timezone: Europe/Brussels
+  updates: all
diff --git a/packer/ubuntu-2204.pkr.hcl b/packer/ubuntu-2204.pkr.hcl
new file mode 100644
index 0000000..3d49d11
--- /dev/null
+++ b/packer/ubuntu-2204.pkr.hcl
@@ -0,0 +1,115 @@
+packer {
+  required_plugins {
+    name = {
+      version = "~> 1"
+      source  = "github.com/hashicorp/proxmox"
+    }
+    ansible = {
+      version = "~> 1"
+      source = "github.com/hashicorp/ansible"
+    }
+  }
+}
+
+variable "ssh_password_crypt" {
+  type    = string
+  default = null
+}
+
+variable "ssh_user" {
+  type = string
+  default = "packer"
+}
+
+variable "ssh_password" {
+  type = string
+  default = null
+}
+
+variable "proxmox_user" {
+  type = string
+  default = "packer"
+}
+
+variable "proxmox_token" {
+    type = string
+    default = null
+}
+
+variable "proxmox_url" {
+    type = string
+    default = "localhost:8006"
+}
+
+source "proxmox-iso" "ubuntu-2204" {
+    http_content = {
+    "/meta-data" = file("http/meta-data")
+    "/user-data" = templatefile("http/user-data", { user = var.ssh_user, password_crypt = var.ssh_password_crypt })
+    }
+    
+
+    boot_command = [
+      "e<wait><down><down><down><end>",
+      " autoinstall ds=\"nocloud-net;seedfrom=http://{{.HTTPIP}}:{{.HTTPPort}}/\"",
+      "<leftCtrlOn>x<leftCtrlOff>",
+    ]
+
+
+    boot_wait = "10s"
+
+    disks {
+        disk_size         = "10G"
+        storage_pool      = "lab"
+        type              = "virtio"
+    }
+    scsi_controller       = "virtio-scsi-single"
+
+    bios  = "ovmf"
+    efi_config {
+        efi_storage_pool  = "lab"
+        efi_type          = "4m"
+        pre_enrolled_keys = true
+    }
+
+    insecure_skip_tls_verify = true
+
+    iso_checksum            = "45f873de9f8cb637345d6e66a583762730bbea30277ef7b32c9c3bd6700a32b2"
+    iso_url                 = "https://releases.ubuntu.com/22.04.4/ubuntu-22.04.4-live-server-amd64.iso"
+    iso_storage_pool        = "local"
+    iso_download_pve        = true
+    unmount_iso             = true
+    
+    network_adapters {
+        bridge = "vmbr20"
+        model  = "virtio"
+    }
+
+    memory               = 8192
+    cores                = 4
+    vm_id                = 9000
+
+    node                 = "sorm"
+    username             = "${var.proxmox_user}"
+    token                = "${var.proxmox_token}"
+    proxmox_url          = "${var.proxmox_url}/api2/json"
+    ssh_username         = "${var.ssh_user}"
+    ssh_private_key_file = "~/.ssh/id_ed25519"
+    ssh_timeout          = "15m"
+    template_description = "Ubuntu 22.04 build by packer on ${timestamp()}"
+    template_name        = "ubuntu-2204-r4"
+    cloud_init           = true
+    cloud_init_storage_pool = "local-lvm"
+}
+
+
+build {
+  sources = ["source.proxmox-iso.ubuntu-2204"]
+  provisioner "ansible" {
+      ansible_env_vars = ["ANSIBLE_CONFIG=../ansible/ansible.cfg", "ANSIBLE_HOST_KEY_CHECKING=False", "ANSIBLE_BECOME_PASS=${var.ssh_password}"]
+      extra_arguments  = [ "--scp-extra-args", "'-O'", "-vv", "--extra-vars", "ansible_become_password=${var.ssh_password}" ]
+      command          = "ansible-playbook"
+      roles_path       = "../ansible/roles"
+      playbook_file    = "../ansible/packer.yml"
+  }
+
+}