Blog
No items found.
5
minutes

How to migrate your PostHog self-hosted to PostHog Cloud

PostHog is an open-source product analytics tool that we use at Qovery to improve the developer experience. PostHog is similar to famous proprietary product analytics tools like Mixpanel, Heap, Amplitude.
Romaric Philogène
CEO & Co-founder
Summary
Twitter icon
linkedin icon

At Qovery, we were using PostHog self-hosted for 8 months in production. It was running nicely on Qovery (yes, we deployed PostHog with Qovery 😎 #eatYourOwnDogFood), but we decided to move to the PostHog Cloud. Here are the two main reasons why we decided to make this move:

If you are not yet using it, give it a try with Qovery or PostHog Cloud version.
  1. To support the PostHog project: Because we love their product, keeping them by using their Cloud version makes complete sense to us.
  2. Stay focused on our business: Using the self-hosted version of PostHog requires you to spend time to maintain it. Meaning, you have to handle the upgrade yourself and make sure the service is up and running all day long.

How to migrate

Looking at their documentation, there is no guide to migrate from PostHog self-hosted to their Cloud version. I asked them the procedure on Slack, and Paolo from the PostHog team responded that it should not be too complicated to transfer the data by fetching the data from the PostHog source and pushing them to the PostHog destination via the web API.

Conversation with Paolo from Posthog on Slack

So the idea was to make a Python script to fetch the data from our self-hosted PostHog instance and forward the data to the PostHog Cloud version.

(Self-hosted PostHog) <--[fetch event data via web API]-- Python Script --[send event data via web API]--> (PostHog Cloud)

Before migrating

As I know that we have more than one million events to send, I notified the PostHog support team that we will migrate our self-hosted version. Just in case they have to adjust their infrastructure. Who knows :) Paolo (once again, he is everywhere 🙂) responded to me and was super proactive.

null
null

I encourage you always to keep informed the support team of service when you are about to migrate. They can help you. It was the case here with the PostHog support team.

We can migrate now

To migrate, I made a simple Python script using no external dependencies. Only HTTP requests, and that's it. Note: This script is compatible with Python 3.6+.

#!/usr/bin/env python
import copy
import time
import uuid
from urllib.parse import urlparse

import requests

# Your source PostHog instance
source_posthog_scheme_and_host = 'https://posthog.your-domain.tld'

# Generate a personal API key to read the data from your source PostHog instance
source_api_key = 'xxx'

# Your project API key provided by PostHog in your project settings
dest_project_api_key = 'xxx'


def is_valid(key: str, data: dict) -> bool:
"""
Helper function to check if the value is empty or none
:param key: data key to check
:param value: dict
:return: true if valid (not empty, None), false otherwise
"""
if key not in data or not data[key] or str(data[key]).strip().lower() == 'none':
return False

return True


def clean_source_data(results: [dict]) -> [dict]:
"""
Clean up data from PostHog source
Note: this function do not mutate `results`
:param results:
:return:
"""
_results = []
if not results:
return _results

distinct_id = 'distinct_id'

for result in results:
data = copy.deepcopy(result)
del data['id']

if not is_valid(distinct_id, data):
data[distinct_id] = str(uuid.uuid4())

if 'properties' in data:
properties = data['properties']
if not is_valid(distinct_id, properties):
# copy distinct_id value from the parent object
properties[distinct_id] = data[distinct_id]

_results.append(data)

return _results


def capture(data: [dict], count: int = 0):
"""
Function to send data to the dest PostHog instance
:param data:
:param count:
:return:
"""
res = requests.post('https://app.posthog.com/capture', json={'api_key': dest_project_api_key, 'batch': data},
headers={'Content-type': 'application/json'})

if res.status_code != 200:
if count >= 100:
print('retry exceeded')
exit(1)
time.sleep(3)
print('Retry sending data (status code: {}) to dest PostHog with data {}'.format(res.status_code, data))
return capture(data, count + 1)

return res


def get_source_data() -> dict:
headers = {'Authorization': 'Bearer {}'.format(source_api_key)}

url = '{}/api/event'.format(source_posthog_scheme_and_host)

with open('posthog_completed_urls', 'a') as f:
while 1:
query = urlparse(url).query
if query:
# PostHog return weird next URL with tons of 'before' params
url = '{}/api/event?before={}'.format(source_posthog_scheme_and_host, query.split('=')[-1])

res = requests.get(url, headers=headers)
if res.status_code == 200:
j_res = res.json()
_data = clean_source_data(j_res['results'])

yield _data

f.write(url + '\n')
url = j_res['next']
else:
print('Retry fetching events (status code: {}) from PostHog source with URL {}'.format(res.status_code, url))
time.sleep(3)


if __name__ == '__main__':
for data in get_source_data():
capture(data)
print('{} lines migrated'.format(len(data)))

print('ok')
Before running the migration script, I strongly recommend making your apps send the data to the PostHog Cloud instance before and waiting for the old instance to stop receiving new events.

Now it is time to:

  1. Copy this script.
  2. Change the value of the variables source_posthog_scheme_and_host, source_api_key, dest_project_api_key
  3. Run the script and wait until it is done 👌

Wrapping up

The migration went smoothly and took one day because we had more than 1 million events. We are super excited to use PostHog Cloud. It is fast and efficient for improving the developer experience on Qovery. Any questions about our usage of PostHog? Join our Discord to chat about it.

Resources:

  • PostHog: open-source product analytics tool.
  • Qovery: the simplest way to deploy your apps on AWS.
Share on :
Twitter icon
linkedin icon
Ready to rethink the way you do DevOps?
Qovery is a DevOps automation platform that enables organizations to deliver faster and focus on creating great products.
Book a demo

Suggested articles

AI
Infrastructure Management
Product
5
 minutes
GPU workloads on EKS just got way simpler with Qovery

Running GPU workloads on EKS has never been easy, until now. With Qovery’s latest update, you can enable GPU nodes, configure GPU access, and optimize costs automatically, all without writing a single line of YAML or touching Helm charts. Qovery now handles everything behind the scenes so you can focus entirely on your applications.

Alessandro Carrano
Lead Product Manager
Kubernetes
 minutes
Kubernetes Deployment Strategies: Pros, Cons & Use Cases

Master Kubernetes deployment strategies: Rolling Update, Recreate, Blue/Green, and Canary. Learn the pros, cons, and use cases to choose the right strategy based on your uptime, risk tolerance, and resources. Simplify complex rollouts with automation.

Mélanie Dallé
Senior Marketing Manager
DevOps
Developer Experience
 minutes
AWS ECS vs. EKS vs. Elastic Beanstalk: A Comprehensive Guide

Confused about which AWS container service to use? This comprehensive guide compares the trade-offs between simplicity, control, and complexity for ECS, EKS, and Elastic Beanstalk to help you choose the right platform for your application.

Mélanie Dallé
Senior Marketing Manager
DevOps
AWS
7
 minutes
Migrating from ECS to EKS: A Complete Guide

Planning your ECS to EKS migration? Learn the strategic business case (portability, ecosystem access), navigate the step-by-step roadmap, and avoid common pitfalls (networking, resource allocation). Discover how Qovery automates EKS complexity for a seamless transition.

Morgan Perry
Co-founder
DevOps
 minutes
Fargate Simplicity vs. Kubernetes Power: Where Does Your Scaling Company Land?

Is Fargate too simple or Kubernetes too complex for your scale-up? Compare AWS Fargate vs. EKS on cost, control, and complexity. Then, see how Qovery automates Kubernetes, giving you its power without the operational headache or steep learning curve.

Mélanie Dallé
Senior Marketing Manager
DevOps
Cloud Migration
 minutes
FluxCD vs. ArgoCD: Why Qovery is the Better Way to Do GitOps

Dive into the ultimate FluxCD vs. ArgoCD debate! Learn the differences between these top GitOps tools (CLI vs. UI, toolkit vs. platform) and discover a third path: Qovery, the DevOps automation platform that abstracts away Kubernetes complexity, handles infrastructure, and lets you ship code faster.

Mélanie Dallé
Senior Marketing Manager
Qovery
 minutes
Our rebrand: setting a new standard for DevOps automation

Qovery unveils its new brand identity, reinforcing its mission to make DevOps simple, intuitive, and powerful. Discover how our DevOps automation platform simplifies infrastructure, scaling, security, and innovation across the full DevOps lifecycle.

Romaric Philogène
CEO & Co-founder
Qovery
3
 minutes
We've raised $13M Series A to make DevOps so simple, it feels unfair

I'm excited to announce our $13M Series A, led by IRIS and Crane Venture Partners with support from Datadog founders and Speedinvest. This investment will fuel our mission to make DevOps simple and scalable, expand in the US and Europe, and accelerate product innovation.

Romaric Philogène
CEO & Co-founder

It’s time to rethink
the way you do DevOps

Say goodbye to DevOps overhead. Qovery makes infrastructure effortless, giving you full control without the trouble.