diff --git a/infra/rmfakecloud/0000_proxmox_playbook.yaml b/infra/rmfakecloud/0000_proxmox_playbook.yaml
new file mode 100644
index 0000000..564bf5a
--- /dev/null
+++ b/infra/rmfakecloud/0000_proxmox_playbook.yaml
@@ -0,0 +1,112 @@
+- name: Provision rmfakecloud Proxmox VM
+  hosts: rmfakecloud
+  connection: ansible.builtin.local
+  gather_facts: false
+  vars:
+    api_user: "{{ lookup('ansible.builtin.env', 'PROXMOX_USER') }}"
+    api_host: "{{ lookup('ansible.builtin.env', 'PROXMOX_HOST' ) }}"
+    api_token_id: "{{ lookup('ansible.builtin.env', 'PROXMOX_TOKEN_ID') }}"
+    api_token_secret: "{{ lookup('ansible.builtin.env', 'PROXMOX_TOKEN_SECRET') }}"
+    ssh_public: "{{ lookup('ansible.builtin.env', 'SSH_PUBLIC') }}"
+    vmname: "{{ inventory_hostname | regex_replace('^([^\\.]+)\\..+$', '\\1') }}"
+    node: pve1
+  module_defaults:
+    community.general.proxmox_kvm:
+      api_user: "{{ api_user }}"
+      api_host: "{{ api_host }}"
+      api_token_id: "{{ api_token_id }}"
+      api_token_secret: "{{ api_token_secret }}"
+      name: "{{ vmname }}"
+      node: "{{ node }}"
+    community.general.proxmox_nic:
+      api_user: "{{ api_user }}"
+      api_host: "{{ api_host }}"
+      api_token_id: "{{ api_token_id }}"
+      api_token_secret: "{{ api_token_secret }}"
+      name: "{{ vmname }}"
+    community.general.proxmox_disk:
+      api_user: "{{ api_user }}"
+      api_host: "{{ api_host }}"
+      api_token_id: "{{ api_token_id }}"
+      api_token_secret: "{{ api_token_secret }}"
+      name: "{{ vmname }}"
+  tasks:
+    # Initial setup
+    - name: Create VM
+      community.general.proxmox_kvm:
+        clone: "{{ node }}-debian-12"
+        storage: nvme
+      notify:
+        - Start VM
+        - Wait
+    - name: Wait for status
+      community.general.proxmox_kvm:
+        state: current
+      register: vm
+      retries: 30
+      delay: 10
+      until: vm.status is defined
+
+    # Networking and initial config
+    - name: Add PUB NIC
+      community.general.proxmox_nic:
+        interface: net0
+        firewall: false
+        bridge: PUB
+    - name: Add SRV NIC
+      community.general.proxmox_nic:
+        interface: net1
+        firewall: false
+        bridge: SRV
+    - name: Configure cloud-init
+      community.general.proxmox_kvm:
+        update: true
+        ciuser: debian
+        sshkeys: "{{ ssh_public }}"
+        ipconfig:
+          ipconfig0: ip=dhcp,ip6=auto
+          ipconfig1: ip=dhcp
+    - name: Force all notified handlers to run
+      ansible.builtin.meta: flush_handlers
+
+    # VM Configuration
+    - name: Resize root disk
+      community.general.proxmox_disk:
+        disk: scsi0
+        size: 16G
+        state: resized
+    - name: Create data disk
+      community.general.proxmox_disk:
+        disk: scsi1
+        backup: true
+        storage: nvme
+        size: 16
+    - name: Update VM
+      community.general.proxmox_kvm:
+        update: true
+        agent: enabled=1
+        tags:
+          - debian-12
+          - managed
+        onboot: true
+        cores: 2
+        memory: 4096
+
+    - name: Retart VM
+      community.general.proxmox_kvm:
+        state: restarted
+        timeout: 60
+
+  handlers:
+    # Initial boot
+    # For some reason debian cloud images don't use
+    # cloud-init for networking on first boot (cloud-init files
+    # are regenerated AFTER networking starts). But we need the
+    # hostname to be registered with DHCP later on so ¯\_(ツ)_/¯
+    - name: Start VM
+      community.general.proxmox_kvm:
+        state: started
+      register: start
+    - name: Wait # Initial apt update, apt upgrade, cloud-init
+      ansible.builtin.wait_for:
+        timeout: 90
diff --git a/infra/rmfakecloud/0001_initialise_playbook.yaml b/infra/rmfakecloud/0001_initialise_playbook.yaml
new file mode 100644
index 0000000..086a30c
--- /dev/null
+++ b/infra/rmfakecloud/0001_initialise_playbook.yaml
@@ -0,0 +1,44 @@
+- name: Initialise VM
+  hosts: rmfakecloud
+  gather_facts: false
+  tasks:
+    - name: Wait for connection
+      ansible.builtin.wait_for_connection:
+        timeout: 300
+
+    - name: Install system packages
+      ansible.builtin.apt:
+        update_cache: true
+        pkg:
+          - qemu-guest-agent
+          - parted
+      become: true
+    - name: Enable qemu-guest-agent
+      ansible.builtin.systemd:
+        name: qemu-guest-agent
+        state: started
+        enabled: true
+      become: true
+
+    - name: Create data partition
+      community.general.parted:
+        device: /dev/disk/by-path/pci-0000:00:05.0-scsi-0:0:0:1
+        label: gpt
+        name: data
+        number: 1
+        state: present
+      become: true
+
+    - name: Create data filesystem
+      community.general.filesystem:
+        dev: /dev/disk/by-path/pci-0000:00:05.0-scsi-0:0:0:1-part1
+        fstype: ext4
+      become: true
+    - name: Mount data partition
+      ansible.posix.mount:
+        src: /dev/disk/by-path/pci-0000:00:05.0-scsi-0:0:0:1-part1
+        path: /var/lib/docker
+        fstype: ext4
+        opts: rw,errors=remount-ro,x-systemd.growfs
+        state: mounted
+      become: true
diff --git a/infra/rmfakecloud/0002_docker_playbook.yaml b/infra/rmfakecloud/0002_docker_playbook.yaml
new file mode 100644
index 0000000..94600d2
--- /dev/null
+++ b/infra/rmfakecloud/0002_docker_playbook.yaml
@@ -0,0 +1,49 @@
+- name: Install docker
+  hosts: rmfakecloud
+  gather_facts: false
+  tasks:
+    - name: Wait for connection
+      ansible.builtin.wait_for_connection:
+        timeout: 300
+
+    - name: Install dependencies
+      ansible.builtin.apt:
+        update_cache: true
+        pkg:
+          - curl
+          - python3-apt
+          - gpg
+      become: true
+
+    - name: Add docker key
+      ansible.builtin.apt_key:
+        url: https://download.docker.com/linux/debian/gpg
+        keyring: /etc/apt/keyrings/docker.gpg
+      become: true
+    - name: Add docker repo
+      ansible.builtin.apt_repository:
+        repo: deb [arch=amd64 signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian bookworm stable
+      become: true
+    - name: Install docker
+      ansible.builtin.apt:
+        update_cache: true
+        pkg:
+          - docker-ce
+          - docker-ce-cli
+          - containerd.io
+          - docker-buildx-plugin
+          - docker-compose-plugin
+      become: true
+    - name: Add user to docker group
+      ansible.builtin.user:
+        user: debian
+        groups:
+          - docker
+        append: true
+      become: true
+    - name: Enable docker
+      ansible.builtin.systemd:
+        name: docker
+        state: started
+        enabled: true
+      become: true
diff --git a/infra/rmfakecloud/0003_rmfakecloud_playbook.yaml b/infra/rmfakecloud/0003_rmfakecloud_playbook.yaml
new file mode 100644
index 0000000..3c2d613
--- /dev/null
+++ b/infra/rmfakecloud/0003_rmfakecloud_playbook.yaml
@@ -0,0 +1,40 @@
+- name: Deploy app
+  hosts: rmfakecloud
+  gather_facts: false
+  vars:
+    app: rmfakecloud
+  tasks:
+    - name: Wait for connection
+      ansible.builtin.wait_for_connection:
+        timeout: 300
+
+    - name: Check if project exists
+      ansible.builtin.stat:
+        path: "$HOME/{{ app }}"
+      register: project
+    - name: Docker compose down
+      when: project.stat.exists
+      community.docker.docker_compose_v2:
+        project_src: "$HOME/{{ app }}"
+        state: absent
+    - name: Copy project
+      ansible.builtin.copy:
+        src: "./{{ app }}"
+        dest: "$HOME"
+        mode: "0744"
+
+    - name: Replace JWT_SECRET_KEY secret
+      ansible.builtin.replace:
+        path: "$HOME/{{ app }}/.env"
+        regexp: "JWT_SECRET_KEY_VALUE"
+        replace: "{{ lookup('infisical.vault.read_secrets', project_id=infisical_project, env_slug='prod',
+                            path='/rmfakecloud', secret_name='JWT_SECRET_KEY')['value'] }}"
+    - name: Replace SMTP Password secret
+      ansible.builtin.replace:
+        path: "$HOME/{{ app }}/.env"
+        regexp: "SMTP_PASSWORD_VALUE"
+        replace: "{{ lookup('ansible.builtin.env', 'SMTP_PASSWORD') }}"
+
+    - name: Docker compose up
+      community.docker.docker_compose_v2:
+        project_src: "$HOME/{{ app }}"
diff --git a/infra/rmfakecloud/rmfakecloud/.env b/infra/rmfakecloud/rmfakecloud/.env
new file mode 100644
index 0000000..cbbbdb8
--- /dev/null
+++ b/infra/rmfakecloud/rmfakecloud/.env
@@ -0,0 +1,10 @@
+# General
+JWT_SECRET_KEY=JWT_SECRET_KEY_VALUE
+PORT=3000
+RM_TRUST_PROXY=10.4.0.1
+
+# Email
+RM_SMTP_SERVER=mx.koval.net:465
+RM_SMTP_USERNAME=no-reply@koval.net
+RM_SMTP_PASSWORD=SMTP_PASSWORD_VALUE
+RM_SMTP_FROM=ReMarkable.koval.net <no-reply@koval.net>
diff --git a/infra/rmfakecloud/rmfakecloud/.gitignore b/infra/rmfakecloud/rmfakecloud/.gitignore
new file mode 100644
index 0000000..fab02bf
--- /dev/null
+++ b/infra/rmfakecloud/rmfakecloud/.gitignore
@@ -0,0 +1 @@
+!*.env
diff --git a/infra/rmfakecloud/rmfakecloud/docker-compose.yml b/infra/rmfakecloud/rmfakecloud/docker-compose.yml
new file mode 100644
index 0000000..894d8f4
--- /dev/null
+++ b/infra/rmfakecloud/rmfakecloud/docker-compose.yml
@@ -0,0 +1,10 @@
+services:
+  rmfakecloud:
+    image: ddvk/rmfakecloud
+    restart: unless-stopped
+    env_file:
+      - .env
+    volumes:
+      - data:/data
+volumes:
+  data:
diff --git a/inventory/proxmox.yaml b/inventory/proxmox.yaml
index 493a72c..d317e6f 100644
--- a/inventory/proxmox.yaml
+++ b/inventory/proxmox.yaml
@@ -29,6 +29,9 @@ proxmox:
             solidtime:
               hosts:
                 solidtime.srv.home.local.koval.net:
+            rmfakecloud:
+              hosts:
+                rmfakecloud.srv.home.local.koval.net:
           vars:
             ansible_user: debian
             ansible_ssh_private_key_file: ~/.ssh/id_rsa