Self-hosted service
k0rventen/apple-health-grafana avatar
k0rventen/apple-health-grafana

apple-health-grafana: reading years of Apple Health exports as Influx time series

Visualise your Apple Health export in Grafana

586 stars42 forksPythonLicense varies

At a glance

What is it?
apple-health-grafana unzips an Apple Health XML export, loads the records into InfluxDB and renders dashboards in Grafana, with dedicated views for fitness, workout routes and sleep stages. It is a one-shot import, not a live sync, and the README says so plainly.
Who is it for?
Adopt apple-health-grafana if you want to look at years of your own Apple Health history in Grafana, or to bring a summary of it to a medical appointment, since that is the use case the project is built around. Do not adopt it as a live tracking pipeline, because the tool imports an export once and does not sync on a schedule.
Can I use it commercially?
Not without permission. GitHub finds no licence file in the repository, and without a licence all rights are reserved by default: you may read the code but not reuse it. Check the README, or ask the authors, before using it.
Is it still maintained?
Yes. The repository last received commits 148 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A retrospective importer, and the README says so

The project is a tool to import your Apple Health data into Influx and visualise it in Grafana at a point in time. The very next paragraph sets the boundary, and it is the most useful sentence in the documentation: this is not a live dashboard for uploading your activities routinely, for example daily, but a tool for visualising long term trends, aggregated statistics over a bigger timespan, or for showing up at a medical appointment with some data to show.

That framing decides the architecture. This is a batch job with a Grafana front end, not a continuous sync. The user exports their health data from the iPhone as a zip, points the ingester at that file, and gets a database they can then query any way they like.

The audience is therefore someone with an existing archive and a specific question about it, rather than someone building a fitness dashboard that updates in real time. The three example screenshots in the repository match that: an overall fitness, energy and activity dashboard, a workout routes view, and a sleep tracking view.

The primary language is Python, the default branch is main, and the topics list apple, apple-health, health-data, influxdb, grafana and docker among others. One thing to note before going further: the repository licence field reads unknown, with no licence file listed in the tree, so the licensing terms are not something the repository itself states.

Getting the export off the iPhone

The data source is Apple's own export, and the README quotes Apple's support documentation for it.

The steps, as documented, are to share your health and fitness data in XML format. You tap your profile picture or initials at the top right, and if you do not see the profile picture or initials, you tap Summary or Browse at the bottom of the screen and scroll to the top. Then you tap Export all health data and choose a method for sharing your data.

That produces a .zip file that can be shared from the iPhone via AirDrop, messages, mail and so on.

There is a known failure mode, and the workaround is in the documentation rather than hidden. If you encounter a Could not export data message, increase the Auto-Lock timer of your iPhone under Settings, Display and Brightness, then try again. That makes sense as a fix, because the export is an interactive operation on the device and the screen locking mid-flow is what interrupts it.

Once the file is copied or shared to your computer, note the path of the file, which the README suggests will look something like /home/me/downloads/export.zip. The path matters in the next step, because the compose file mounts that exact file into the ingester container.

The compose file, and the one line you have to edit

You need docker and docker-compose installed. The compose file itself is fetched from the repository, and the README gives three ways to do that.

bash
# wget
wget https://raw.githubusercontent.com/k0rventen/apple-health-grafana/main/docker-compose.yml

# curl
curl -sfLo docker-compose.yml https://raw.githubusercontent.com/k0rventen/apple-health-grafana/main/docker-compose.yml

# httpie
https --download https://raw.githubusercontent.com/k0rventen/apple-health-grafana/main/docker-compose.yml

Then there is exactly one line to change, the volume that mounts your export into the ingester, which ships as a placeholder.

yaml
    volumes:
    - <local_path_to_export.zip>:/export.zip

You replace the placeholder with your actual path, for example the path you noted in the previous step.

yaml
    volumes:
    - /home/me/downloads/export.zip:/export.zip

And you can also use a relative path, assuming you are in the same directory as the compose file.

yaml
    volumes:
    - ./export.zip:/export.zip

That relative form is the one to prefer, because it makes the compose file portable between machines. Nothing else in the file needs touching.

The services themselves are visible in the compose file in the repository root. Grafana runs from the image k0rventen/apple-health-grafana-grafana:v0.7.0 with port 3000 published, Influx runs from influxdb:1.12 with the environment variable INFLUXDB_DB set to health and a named volume influx-data at /var/lib/influxdb, and the ingester runs from k0rventen/apple-health-grafana-ingester:v0.7.0 with the export mounted at /export.zip.

Running the import and reading its log output

The stack is started in two steps, and the order is deliberate. Grafana and Influx start in the background first, and the ingester runs in the foreground afterwards.

bash
# start grafana and influx in the background
docker compose up -d grafana influx

# start our ingester
docker compose up ingester

The ingester waits for Influx to be ready, so starting the databases first is what makes the run work rather than fail on the first attempt.

You should see logs from the ingester container, and the documentation shows what a successful run looks like: unzipping the export file, the export file unzipped, influx is ready, loading workout routes, an opening Route line for each route, and then the record counts.

The final lines are the ones to wait for: inserted records, the total number of records, All done ! You can now check grafana, and the container exiting with code 0.

The README is explicit that you should wait for a log saying that all the data have been imported. Depending on the amount of data in the export, it can take a few minutes to work through, and the example given is loading nearly 4 years of data, with 3 millions plus data points and 200 plus workout routes, on a 10th gen i5 taking around 5 minutes.

The volume of numbers in that log is a good sanity check on your own import, and the route lines are worth reading too, because a route that fails to open is a route that will be missing from the map dashboard.

Two failure modes the documentation handles head on

Two problems come up often enough that the README has a section for each, and they are worth reading before your first run rather than after it.

The first is a borked export. The documentation says it is not uncommon for the export to be borked, citing three separate issues, and that some preventive measures are in place to fix it as best as possible, but if you encounter weird errors or behavior, you should try again with a new export. The practical reading of that is that a failed import is usually not a bug in the tool but a bad input file, and retrying the export from the phone is the fix.

The second is a limit on open files, and it has a concrete command. If you have a very large export file, above 100Mo and likely 5 plus years of data, you may encounter an error related to limits: too many open files, tracked as issue 35. The fix is to increase the limit by running ulimit -n 1000000, and then relaunching the whole compose stack.

That is a host-level setting rather than a container setting, which is why the fix has to be applied before the stack starts, and why the documentation says to relaunch everything rather than just the ingester.

Neither of these is a sign of an unmaintained project. They are the predictable consequences of loading millions of records from a zip archive into a time series database, and the documentation names them, links the issues, and gives the workaround. That is more than a lot of projects do.

The four dashboards and where the credentials are

Once the import finishes, you head to http://localhost:3000 and log in with the Grafana credentials from the compose file, which default to admin and health.

The default credentials being a well-known pair is worth noting for anything beyond a local machine, and it is a small thing to change once the data is in there, since the data is personal health history.

Four dashboards are created by default. The first is a generic one displaying every metric available. The second is a more refined one for specific metrics that are probably there, like walking distance and heart related metrics. The third is a workout routes one, which shows a GPS map of your outdoor routes for walking, running and biking. The fourth is a sleep tracking dashboard, which displays how much time and what percentage is spent in each sleep stage.

That last one is the reason the tool is interesting beyond curiosity. Apple records sleep in stages, and having them in a database where you can aggregate over years changes the question you can ask.

The README also has a short section on further analysis, and it is where the real value of a time series database becomes clear. Some metrics can be displayed as they are, but others need tweaking in the Influx request, for example adjusting the time interval to 1d to group daily metrics, or using sum() instead of mean() to aggregate the metrics for a given interval.

That is the point of the whole exercise. Once the data is in Influx, the dashboards Grafana ships are just four starting views, and the querying is where you can ask your own questions.

Running the ingester from source instead of the registry images

For development, the project provides a second compose file. The documentation says just compose is needed, and to use the -dev spec yaml instead to build the ingester and grafana image rather than using the registry ones.

bash
# launch the db 
docker compose -f docker-compose-dev.yaml up -d influx

# build and launch grafana
docker compose -f docker-compose-dev.yaml up --build -d grafana

# same with the ingester
docker compose -f docker-compose-dev.yaml up --build ingester

The pattern is the same as the production path, with --build added and the file swapped. The influx service still comes from the registry, and only the ingester and the Grafana image are built locally.

One wrinkle in the repository tree is worth flagging: the top level contains docker-compose-dev.yml while the documented commands refer to docker-compose-dev.yaml. The extensions differ, and the commands as written are what the README specifies, so if you hit a file-not-found error on the dev path that is the first thing to check.

The tree also holds ingester/, grafana/ and tests/ directories, so the ingester and the dashboard provisioning are separate pieces of the project from the compose glue, and the ingester has its own test suite.

On versions and maintenance, the recent releases are v0.7.0 on 2026-05-28, v0.6.0 on 2025-12-25 and v0.5.0 on 2024-05-19, and the compose file pins its images to the v0.7.0 tags, so the stack and the released version stay in step. The repository is not archived and the last push was on 2026-05-28.

Editorial conclusion

Adopt apple-health-grafana if you want to look at years of your own Apple Health history in Grafana, or to bring a summary of it to a medical appointment, since that is the use case the project is built around. Do not adopt it as a live tracking pipeline, because the tool imports an export once and does not sync on a schedule. Before you start, note the licence is not stated in the repository metadata, the InfluxDB image is the 1.12 series, and the last push was on 2026-05-28 with release v0.7.0 the same day.

Frequently asked questions

How do I get my Apple Health data out of my iPhone?

In the Health app, tap your profile picture or initials at the top right, tap Export all health data and choose a sharing method. If you get a Could not export data message, increase the Auto-Lock timer under Settings, Display and Brightness and try again. The result is a .zip file.

Is apple-health-grafana a live dashboard that syncs automatically?

No. The project states it is not a live dashboard for uploading activities routinely, but a tool for visualising long term trends, aggregated statistics over a bigger timespan, or for showing data at a medical appointment. You import an export and then query it.

What do I need to change in the docker-compose file?

One line. The volume that mounts the export ships as a placeholder, and you replace <local_path_to_export.zip> with the path of your export file, for example /home/me/downloads/export.zip, or a relative path such as ./export.zip if you are in the same directory as the compose file.

What should I do if the import fails with too many open files?

That happens with a very large export, above 100Mo and likely 5 plus years of data. Increase the limit by running ulimit -n 1000000 and then relaunch the whole compose stack, since it is a host-level setting rather than a container one.

Which Grafana dashboards are created and what are the default credentials?

Four: a generic dashboard with every metric, a refined one for common metrics like walking distance and heart related metrics, a workout routes dashboard with a GPS map, and a sleep tracking dashboard showing time and percentage per sleep stage. You log in at http://localhost:3000 with admin and health by default.

Official sources

  1. Issues
  2. k0rventen/apple-health-grafana on GitHub
  3. README
  4. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/k0rventen-apple-health-grafana.svg)](https://hysenlabs.com/projects/k0rventen-apple-health-grafana)

Was this useful?