diff --git a/ee/app/services/geo/container_repository_sync.rb b/ee/app/services/geo/container_repository_sync.rb
index a1ced0c0b19ec130ee0523841418dea822e1ff7a..d6e3b6765f886f495bc1a03b82fa7c64e4bdf576 100644
--- a/ee/app/services/geo/container_repository_sync.rb
+++ b/ee/app/services/geo/container_repository_sync.rb
@@ -35,9 +35,10 @@ def execute
 
     def sync_tag(tag)
       file = nil
-      manifest = client.repository_manifest(name, tag)
+      manifest = client.repository_raw_manifest(name, tag)
+      manifest_parsed = JSON.parse(manifest)
 
-      list_blobs(manifest).each do |digest|
+      list_blobs(manifest_parsed).each do |digest|
         next if container_repository.blob_exists?(digest)
 
         file = client.pull_blob(name, digest)
@@ -45,7 +46,7 @@ def sync_tag(tag)
         file.unlink
       end
 
-      container_repository.push_manifest(tag, manifest, manifest['mediaType'])
+      container_repository.push_manifest(tag, manifest, manifest_parsed['mediaType'])
     ensure
       file.try(:unlink)
     end
diff --git a/ee/lib/ee/container_registry/client.rb b/ee/lib/ee/container_registry/client.rb
index 6f840d591bbc321967a1ad7a0d425c6bd2c61b6d..b88497317941cca9b120fe3c09996133d3e3d545 100644
--- a/ee/lib/ee/container_registry/client.rb
+++ b/ee/lib/ee/container_registry/client.rb
@@ -3,6 +3,8 @@
 module EE
   module ContainerRegistry
     module Client
+      include ::Gitlab::Utils::StrongMemoize
+
       Error = Class.new(StandardError)
 
       # In the future we may want to read a small chunks into memory and use chunked upload
@@ -12,7 +14,7 @@ def push_blob(name, digest, file_path)
         url = get_upload_url(name, digest)
         headers = { 'Content-Type' => 'application/octet-stream', 'Content-Length' => payload.size.to_s }
 
-        response = faraday_upload.put(url, payload, headers)
+        response = faraday.put(url, payload, headers)
 
         raise Error.new("Push Blob error: #{response.body}") unless response.success?
 
@@ -51,6 +53,10 @@ def pull_blob(name, digest)
         file.close
       end
 
+      def repository_raw_manifest(name, reference)
+        response_body faraday_raw.get("/v2/#{name}/manifests/#{reference}")
+      end
+
       private
 
       def get_upload_url(name, digest)
@@ -58,21 +64,24 @@ def get_upload_url(name, digest)
 
         raise Error.new("Get upload URL error: #{response.body}") unless response.success?
 
-        response.headers['location']
-
         upload_url = URI(response.headers['location'])
         upload_url.query = "#{upload_url.query}&#{URI.encode_www_form(digest: digest)}"
         upload_url
       end
 
-      def faraday_upload
-        @faraday_upload ||= Faraday.new(@base_uri) do |conn| # rubocop:disable Gitlab/ModuleWithInstanceVariables
-          initialize_connection(conn, @options) # rubocop:disable Gitlab/ModuleWithInstanceVariables
-          conn.request :multipart
-          conn.request :url_encoded
-          conn.adapter :net_http
+      # rubocop:disable Gitlab/ModuleWithInstanceVariables
+      def faraday_raw
+        strong_memoize(:faraday_raw) do
+          Faraday.new(@base_uri) do |conn|
+            initialize_connection(conn, @options, &method(:accept_raw_manifest))
+          end
         end
       end
+      # rubocop:enable Gitlab/ModuleWithInstanceVariables
+
+      def accept_raw_manifest(conn)
+        conn.headers['Accept'] = ::ContainerRegistry::Client::ACCEPTED_TYPES
+      end
     end
   end
 end
diff --git a/ee/spec/lib/container_registry/client_spec.rb b/ee/spec/lib/container_registry/client_spec.rb
index dc58410771de4db232c94bc90ecaf54b96290de7..f7f2fecf0fade2be61be285ced95419bb72a33f1 100644
--- a/ee/spec/lib/container_registry/client_spec.rb
+++ b/ee/spec/lib/container_registry/client_spec.rb
@@ -121,4 +121,20 @@
       expect(client.blob_exists?('group/test', digest)).to eq(false)
     end
   end
+
+  describe '#repository_raw_manifest' do
+    let(:manifest) { '{schemaVersion: 2, layers:[]}' }
+
+    it 'GET "/v2/:name/manifests/:reference' do
+      stub_request(:get, 'http://registry/v2/group/test/manifests/my-tag')
+        .with(
+          headers: {
+            'Accept' => 'application/vnd.docker.distribution.manifest.v2+json, application/vnd.oci.image.manifest.v1+json',
+            'Authorization' => 'bearer 12345'
+          })
+        .to_return(status: 200, body: manifest, headers: {})
+
+      expect(client.repository_raw_manifest('group/test', 'my-tag')).to eq(manifest)
+    end
+  end
 end
diff --git a/ee/spec/services/geo/container_repository_sync_spec.rb b/ee/spec/services/geo/container_repository_sync_spec.rb
index 995cc1d132dc80cdb080ed441ede6c194a1649b2..6a36c14b18e1d05b576966408a343258caadf840 100644
--- a/ee/spec/services/geo/container_repository_sync_spec.rb
+++ b/ee/spec/services/geo/container_repository_sync_spec.rb
@@ -10,6 +10,11 @@
     create(:container_repository, name: 'my_image', project: project)
   end
 
+  # Break symbol will be removed if JSON encode/decode operation happens
+  # so we use this to prove that it does not happen and we preserve original
+  # human readable JSON
+  let(:manifest) { "{\"schemaVersion\":2,\n\"layers\":[]}" }
+
   before do
     stub_container_registry_config(enabled: true,
                                    api_url: 'http://registry.gitlab',
@@ -52,12 +57,30 @@
           'Authorization' => 'bearer token'
         })
       .to_return(status: 200, body: "", headers: { 'docker-content-digest' => 'sha256:aaaaa' })
+
+    stub_request(:get, "http://primary.registry.gitlab/v2/group/test/my_image/manifests/tag-to-sync")
+      .with(
+        headers: {
+          'Accept' => 'application/vnd.docker.distribution.manifest.v2+json, application/vnd.oci.image.manifest.v1+json',
+          'Authorization' => 'bearer pull-token'
+        })
+      .to_return(status: 200, body: manifest, headers: {})
+
+    stub_request(:put, "http://registry.gitlab/v2/group/test/my_image/manifests/tag-to-sync")
+      .with(
+        body: manifest,
+        headers: {
+          'Accept' => 'application/vnd.docker.distribution.manifest.v2+json, application/vnd.oci.image.manifest.v1+json',
+          'Authorization' => 'bearer token',
+          'Content-Type' => 'application/json'
+        })
+      .to_return(status: 200, body: "", headers: {})
   end
 
   describe 'execute' do
     it 'determines list of tags to sync and to remove correctly' do
       expect(container_repository).to receive(:delete_tag_by_digest).with('sha256:aaaaa')
-      expect_any_instance_of(described_class).to receive(:sync_tag)
+      expect_any_instance_of(described_class).to receive(:sync_tag).with('tag-to-sync').and_call_original
 
       described_class.new(container_repository).execute
     end