diff --git a/config.yaml b/config.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..14a8473d235a1f861ade93c6ebdedb5b41563edc
--- /dev/null
+++ b/config.yaml
@@ -0,0 +1,4 @@
+version: 3
+metadata_directory: hasura/metadata
+migrations_directory: hasura/migrations
+seeds_directory: hasura/seeds
diff --git a/docker-compose.yml b/docker-compose.yml
index 5a81cfcd1ca163582c81532c7cf127897b01f52a..33355ef7a3f53b112b300cd4433fc26c7ba26b12 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -5,14 +5,24 @@ services:
     restart: always
     volumes:
       - db_data:/var/lib/postgresql/data
+      # - ./hasura/migrations:/docker-entrypoint-initdb.d
     ports:
       - "5432:5432"
     environment:
       POSTGRES_USER: ${DB_USER:-postgres}
       POSTGRES_PASSWORD: ${DB_PASSWORD:-postgrespassword}
       POSTGRES_DB: ${DB_DATABASE:-postgres}
+    healthcheck:
+      test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-postgres} -d ${DB_DATABASE:-postgres}"]
+      interval: 2s
+      timeout: 2s
+      retries: 5
+
   graphql-engine:
     image: hasura/graphql-engine:v2.36.0
+    depends_on:
+      postgres:
+        condition: service_healthy
     ports:
       - "8080:8080"
     restart: always
@@ -36,9 +46,12 @@ services:
       HASURA_GRAPHQL_UNAUTHORIZED_ROLE: public
       ## Disable telemetry
       HASURA_GRAPHQL_ENABLE_TELEMETRY: "false"
+      ## Define metadata and migrations directories
     volumes: # for local developement, you want to record the database migrations in git
-      - ./hasura/migrations:/hasura-migrations
-      - ./hasura/metadata:/hasura-metadata
+      - ./hasura:/hasura
+      - ./scripts/init-hasura.sh:/init-hasura.sh
+      - ./config.yaml:/config.yaml
+    command: sh /init-hasura.sh
 
 volumes:
   db_data:
diff --git a/hasura/metadata/actions.graphql b/hasura/metadata/actions.graphql
new file mode 100644
index 0000000000000000000000000000000000000000..41cb0af51c16839e8ccedc3f329882c0a73d0a89
--- /dev/null
+++ b/hasura/metadata/actions.graphql
@@ -0,0 +1,21 @@
+type Mutation {
+  updateProfile(
+    address: String!
+    hash: String!
+    signature: String!
+    avatarBase64: String
+    description: String
+    geoloc: GeolocInput
+  ): UpdateProfileResponse
+}
+
+input GeolocInput {
+  latitude: Float!
+  longitude: Float!
+}
+
+type UpdateProfileResponse {
+  success: Boolean!
+  message: String!
+}
+
diff --git a/hasura/metadata/actions.yaml b/hasura/metadata/actions.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..04128c4625697499437f7ff3046c4d5e00ce3dff
--- /dev/null
+++ b/hasura/metadata/actions.yaml
@@ -0,0 +1,16 @@
+actions:
+  - name: updateProfile
+    definition:
+      kind: synchronous
+      handler: http://host.docker.internal:3000/update-profile-data
+      timeout: 15
+    permissions:
+      - role: public
+    comment: updateProfile
+custom_types:
+  enums: []
+  input_objects:
+    - name: GeolocInput
+  objects:
+    - name: UpdateProfileResponse
+  scalars: []
diff --git a/hasura/metadata/allow_list.yaml b/hasura/metadata/allow_list.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..fe51488c7066f6687ef680d6bfaa4f7768ef205c
--- /dev/null
+++ b/hasura/metadata/allow_list.yaml
@@ -0,0 +1 @@
+[]
diff --git a/hasura/metadata/api_limits.yaml b/hasura/metadata/api_limits.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..0967ef424bce6791893e9a57bb952f80fd536e93
--- /dev/null
+++ b/hasura/metadata/api_limits.yaml
@@ -0,0 +1 @@
+{}
diff --git a/hasura/metadata/backend_configs.yaml b/hasura/metadata/backend_configs.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..0967ef424bce6791893e9a57bb952f80fd536e93
--- /dev/null
+++ b/hasura/metadata/backend_configs.yaml
@@ -0,0 +1 @@
+{}
diff --git a/hasura/metadata/cron_triggers.yaml b/hasura/metadata/cron_triggers.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..fe51488c7066f6687ef680d6bfaa4f7768ef205c
--- /dev/null
+++ b/hasura/metadata/cron_triggers.yaml
@@ -0,0 +1 @@
+[]
diff --git a/hasura/metadata/databases/databases.yaml b/hasura/metadata/databases/databases.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..65a11b202ed88526ac3cff9018de41a763814fd9
--- /dev/null
+++ b/hasura/metadata/databases/databases.yaml
@@ -0,0 +1,14 @@
+- name: default
+  kind: postgres
+  configuration:
+    connection_info:
+      database_url:
+        from_env: HASURA_GRAPHQL_DATABASE_URL
+      isolation_level: read-committed
+      pool_settings:
+        connection_lifetime: 600
+        idle_timeout: 180
+        max_connections: 50
+        retries: 1
+      use_prepared_statements: true
+  tables: "!include default/tables/tables.yaml"
diff --git a/hasura/metadata/databases/default/tables/public_profiles.yaml b/hasura/metadata/databases/default/tables/public_profiles.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..f94138f5d872f65ba8ad2ae6f088bbcfbc76d7bd
--- /dev/null
+++ b/hasura/metadata/databases/default/tables/public_profiles.yaml
@@ -0,0 +1,25 @@
+table:
+  name: profiles
+  schema: public
+computed_fields:
+  - name: avatar64
+    definition:
+      function:
+        name: bytea_to_base64
+        schema: public
+    comment: convert avatar from bytea to base64
+select_permissions:
+  - role: public
+    permission:
+      columns:
+        - avatar
+        - address
+        - geoloc
+        - description
+        - created_at
+        - updated_at
+      computed_fields:
+        - avatar64
+      filter: {}
+      limit: 500
+    comment: ""
diff --git a/hasura/metadata/databases/default/tables/tables.yaml b/hasura/metadata/databases/default/tables/tables.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..6dc41508ffaf12842ee7663763870f4b4529fc01
--- /dev/null
+++ b/hasura/metadata/databases/default/tables/tables.yaml
@@ -0,0 +1 @@
+- "!include public_profiles.yaml"
diff --git a/hasura/metadata/graphql_schema_introspection.yaml b/hasura/metadata/graphql_schema_introspection.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..61a4dcac291837aacfc6d5e3de7dcc73da320ea3
--- /dev/null
+++ b/hasura/metadata/graphql_schema_introspection.yaml
@@ -0,0 +1 @@
+disabled_for_roles: []
diff --git a/hasura/metadata/hasura_metadata_2023_12_17_08_30_28_184.json b/hasura/metadata/hasura_metadata_2023_12_17_08_30_28_184.json
deleted file mode 100755
index 13378a8ae26c48c0e070982b740064eaaa13a6ba..0000000000000000000000000000000000000000
--- a/hasura/metadata/hasura_metadata_2023_12_17_08_30_28_184.json
+++ /dev/null
@@ -1,176 +0,0 @@
-{
-  "resource_version": 32,
-  "metadata": {
-    "version": 3,
-    "sources": [
-      {
-        "name": "default",
-        "kind": "postgres",
-        "tables": [
-          {
-            "table": {
-              "name": "users",
-              "schema": "public"
-            },
-            "select_permissions": [
-              {
-                "role": "public",
-                "permission": {
-                  "columns": [
-                    "avatar",
-                    "address",
-                    "geoloc",
-                    "description",
-                    "created_at",
-                    "updated_at"
-                  ],
-                  "filter": {},
-                  "limit": 500
-                },
-                "comment": ""
-              }
-            ]
-          }
-        ],
-        "configuration": {
-          "connection_info": {
-            "database_url": {
-              "from_env": "HASURA_GRAPHQL_DATABASE_URL"
-            },
-            "isolation_level": "read-committed",
-            "pool_settings": {
-              "connection_lifetime": 600,
-              "idle_timeout": 180,
-              "max_connections": 50,
-              "retries": 1
-            },
-            "use_prepared_statements": true
-          }
-        }
-      }
-    ],
-    "actions": [
-      {
-        "name": "updateProfile",
-        "definition": {
-          "handler": "http://host.docker.internal:3000/update-user-data",
-          "output_type": "UpdateProfileResponse",
-          "arguments": [
-            {
-              "name": "address",
-              "type": "String!"
-            },
-            {
-              "name": "hash",
-              "type": "String!"
-            },
-            {
-              "name": "signature",
-              "type": "String!"
-            },
-            {
-              "name": "avatarBase64",
-              "type": "String"
-            },
-            {
-              "name": "description",
-              "type": "String"
-            },
-            {
-              "name": "geoloc",
-              "type": "String"
-            }
-          ],
-          "type": "mutation",
-          "kind": "synchronous",
-          "timeout": 15
-        },
-        "comment": "updateProfile",
-        "permissions": [
-          {
-            "role": "public"
-          }
-        ]
-      }
-    ],
-    "custom_types": {
-      "input_objects": [
-        {
-          "name": "GeolocInput",
-          "fields": [
-            {
-              "name": "latitude",
-              "type": "Float!"
-            },
-            {
-              "name": "longitude",
-              "type": "Float!"
-            }
-          ]
-        },
-        {
-          "name": "SampleInput",
-          "fields": [
-            {
-              "name": "address",
-              "type": "String"
-            },
-            {
-              "name": "signature",
-              "type": "String"
-            },
-            {
-              "name": "avatar",
-              "type": "String"
-            },
-            {
-              "name": "description",
-              "type": "String"
-            },
-            {
-              "name": "geoloc",
-              "type": "String"
-            }
-          ]
-        }
-      ],
-      "objects": [
-        {
-          "name": "UpdateUserResponse",
-          "fields": [
-            {
-              "name": "success",
-              "type": "Boolean!"
-            },
-            {
-              "name": "message",
-              "type": "String!"
-            }
-          ]
-        },
-        {
-          "name": "UpdateProfileResponse",
-          "fields": [
-            {
-              "name": "success",
-              "type": "Boolean!"
-            },
-            {
-              "name": "message",
-              "type": "String!"
-            }
-          ]
-        },
-        {
-          "name": "SampleOutput",
-          "fields": [
-            {
-              "name": "accessToken",
-              "type": "String"
-            }
-          ]
-        }
-      ]
-    }
-  }
-}
\ No newline at end of file
diff --git a/hasura/metadata/inherited_roles.yaml b/hasura/metadata/inherited_roles.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..fe51488c7066f6687ef680d6bfaa4f7768ef205c
--- /dev/null
+++ b/hasura/metadata/inherited_roles.yaml
@@ -0,0 +1 @@
+[]
diff --git a/hasura/metadata/metrics_config.yaml b/hasura/metadata/metrics_config.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..0967ef424bce6791893e9a57bb952f80fd536e93
--- /dev/null
+++ b/hasura/metadata/metrics_config.yaml
@@ -0,0 +1 @@
+{}
diff --git a/hasura/metadata/network.yaml b/hasura/metadata/network.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..0967ef424bce6791893e9a57bb952f80fd536e93
--- /dev/null
+++ b/hasura/metadata/network.yaml
@@ -0,0 +1 @@
+{}
diff --git a/hasura/metadata/opentelemetry.yaml b/hasura/metadata/opentelemetry.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..0967ef424bce6791893e9a57bb952f80fd536e93
--- /dev/null
+++ b/hasura/metadata/opentelemetry.yaml
@@ -0,0 +1 @@
+{}
diff --git a/hasura/metadata/query_collections.yaml b/hasura/metadata/query_collections.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..fe51488c7066f6687ef680d6bfaa4f7768ef205c
--- /dev/null
+++ b/hasura/metadata/query_collections.yaml
@@ -0,0 +1 @@
+[]
diff --git a/hasura/metadata/remote_schemas.yaml b/hasura/metadata/remote_schemas.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..fe51488c7066f6687ef680d6bfaa4f7768ef205c
--- /dev/null
+++ b/hasura/metadata/remote_schemas.yaml
@@ -0,0 +1 @@
+[]
diff --git a/hasura/metadata/rest_endpoints.yaml b/hasura/metadata/rest_endpoints.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..fe51488c7066f6687ef680d6bfaa4f7768ef205c
--- /dev/null
+++ b/hasura/metadata/rest_endpoints.yaml
@@ -0,0 +1 @@
+[]
diff --git a/hasura/metadata/version.yaml b/hasura/metadata/version.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..0a70affa4bdaac5b06bfd9793ffc20f5f60e9ea6
--- /dev/null
+++ b/hasura/metadata/version.yaml
@@ -0,0 +1 @@
+version: 3
diff --git a/hasura/migrations/default/1702855724171_init/up.sql b/hasura/migrations/default/1702855724171_init/up.sql
new file mode 100644
index 0000000000000000000000000000000000000000..d0c93b7e3e520a60d52a04f16e471418c76dc111
--- /dev/null
+++ b/hasura/migrations/default/1702855724171_init/up.sql
@@ -0,0 +1,18 @@
+SET check_function_bodies = false;
+CREATE TABLE public.profiles (
+    address text NOT NULL,
+    avatar bytea,
+    description text,
+    geoloc point,
+    created_at timestamp with time zone DEFAULT CURRENT_TIMESTAMP,
+    updated_at timestamp with time zone DEFAULT CURRENT_TIMESTAMP
+);
+CREATE FUNCTION public.bytea_to_base64(data_row public.profiles) RETURNS text
+    LANGUAGE plpgsql STABLE
+    AS $$
+BEGIN
+    RETURN ENCODE(data_row.avatar, 'base64');
+END;
+$$;
+ALTER TABLE ONLY public.profiles
+    ADD CONSTRAINT profiles_pkey PRIMARY KEY (address);
diff --git a/scripts/export-metadata.sh b/scripts/export-metadata.sh
new file mode 100755
index 0000000000000000000000000000000000000000..9a7025903225c80ebaf9fa0f660a5139ae75135f
--- /dev/null
+++ b/scripts/export-metadata.sh
@@ -0,0 +1,9 @@
+#!/bin/bash
+
+endpoint="http://localhost:8080"
+export $(cat .env | grep HASURA_GRAPHQL_ADMIN_SECRET)
+
+hasura metadata export --endpoint $endpoint --admin-secret $HASURA_GRAPHQL_ADMIN_SECRET
+
+# To manually apply saved metadata:
+# hasura metadata apply --endpoint $endpoint --admin-secret $HASURA_GRAPHQL_ADMIN_SECRET
diff --git a/scripts/export-migrations.sh b/scripts/export-migrations.sh
new file mode 100755
index 0000000000000000000000000000000000000000..426a88d4bf614af470a361c5874a9b2d129a234e
--- /dev/null
+++ b/scripts/export-migrations.sh
@@ -0,0 +1,9 @@
+#!/bin/bash
+
+endpoint="http://localhost:8080"
+export $(cat .env | grep HASURA_GRAPHQL_ADMIN_SECRET)
+
+hasura migrate create "init" --from-server --endpoint $endpoint --admin-secret $HASURA_GRAPHQL_ADMIN_SECRET --database-name default
+
+# To manually apply saved migrations:
+# hasura migrate apply --endpoint $endpoint --admin-secret $HASURA_GRAPHQL_ADMIN_SECRET --database-name default
\ No newline at end of file
diff --git a/scripts/init-hasura.sh b/scripts/init-hasura.sh
new file mode 100755
index 0000000000000000000000000000000000000000..ad3cb5b5e0804d04ec0dadbefb920a0d8f24c116
--- /dev/null
+++ b/scripts/init-hasura.sh
@@ -0,0 +1,27 @@
+#!/bin/sh
+
+# start hasura in background
+graphql-engine serve &
+
+[ ! $(which hasura) ] && curl -sL https://github.com/hasura/graphql-engine/raw/stable/cli/get.sh | bash
+
+endpoint="http://localhost:8080"
+
+check_hasura_ready() {
+  response=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:8080/healthz)
+  echo $response
+}
+
+echo "Waiting for hasura..."
+while [ $(check_hasura_ready) -ne 200 ]; do
+  sleep 1
+done
+
+echo "Hasura is ready."
+
+hasura migrate apply --endpoint $endpoint --admin-secret $HASURA_GRAPHQL_ADMIN_SECRET --database-name default
+hasura metadata apply --endpoint $endpoint --admin-secret $HASURA_GRAPHQL_ADMIN_SECRET
+
+while true; do
+  sleep 360
+done