Kimai on Heroku
@ ヨハネス · Monday, Jun 15, 2020 · 6 minute read · Update at Feb 3, 2021

I am running a Kimai Time Tracking instance on my own server. But do I really need my own server for that?


Recently I had to set up an on-premise server for my employer and kept being involved in some webdevelopment and sysadmin work. It was a lot of fun and I started running several private virtual private servers. They were useful for testing before deploying at my employers server and also enabled me to shift my own workflow from the cloud of mega companies to my self hosted apps.

But there is a problem. Sysadmin is a profession not because it is easy, but because errors appear and they need solving. Ultimately, I grew a bit tired of managing my own stack after I am done managing the stack at work. But I want to keep some apps, so let’s try and move to free (when small scale) alternatives.

Heroku offers this in the form of hosting apps in a containerized environment with add-ons like databases and whatnot. So they can give me the Php server and mysql database Kimai needs. The free database is limited to 5mb, if that’s not enough, I will just host it myself. But at least I am free of taking care of the Php stuff.

Whatever, take me to the TL;DR already


SPOILER ALERT - don’t install any php locally, it’s a waste of time

In order to package and test the app locally, we have to install PHP and composer first

  • sudo add-apt-repository ppa:ondrej/php
  • sudo apt install php7.4 unzip

In order to actually run Kimai, we needs some more php extensions

  • sudo apt install php7.4-zip php7.4-gd php7.4-intl php7.4-xml php7.4-xsl php7.4-mbstring php7.4-curl

Time to install composer, some copy pasta for the terminal from this page

php -r "copy('', 'composer-setup.php');"
php -r "if (hash_file('sha384', 'composer-setup.php') === 'e0012edf3e80b6978849f5eff0d4b4e4c79ff1609dd1e613307e16318854d24ae64f26d17af3ef0bf7cfb710ca74755a') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"
php composer-setup.php
php -r "unlink('composer-setup.php');"
mv composer.phar $USER/.local/bin/composer


Now, create an empty project on Heroku using the web interface and clone Kimai into your playground. After that, add the Heroku remote to your local Kimai git repo. Also tell Heroku that its a php app and add the Procfile to use apache pointing at the public directory of Kimai.

  • git clone -b 1.9 --depth 1 && cd kimai2
  • heroku git:remote -a herokuappname
  • heroku buildpacks:set heroku/php
  • echo "web: vendor/bin/heroku-php-apache2 public" > Procfile

Since we will also need a mysql DB, we can continue to configure the Heroku stack. using the website, go to extensions and search for it, or enter the command

  • heroku addons:create jawsdb:kitefin

Since Kimai expects it’s DB string under the environment name DATABASE_URL, create both, the local .env file and also edit the system variable in the Heroku project settings.

  • nano .env
  • DATABASE_URL=mysql://

While we are at it, also supply an actual app-secret to Symfoni

  • APP_SECRET=Whatever

Another important part is to prevent user registration, at least for me it is not needed. Random people can register and see my customers and projects, no thank you. To stop it, we need to create a config file that doesn’t exist by default

  • echo "kimai:\n user:\n registration: false" > config/packages/local.yaml
  • git add -f config/packages/local.yaml

And continue with the Kimai installation.

  • composer install --no-dev --optimize-autoloader

This failed for me with some bug, so I just tried to deploy to Heroku instead

  • git branch master
  • git fetch origin --unshallow
  • git push heroku master

During the first installation I hit a limit on the free database. There were too many migrations going on and it choked. After waiting a minute I tried again and it completed the remaining migrations. So keep that in mind, the install command will fail once. Next we create the admin account.

  • heroku run bash
  • bin/console kimai:install -n; sleep 60; bin/console kimai:install -n
  • bin/console kimai:create-user username ROLE_SUPER_ADMIN

Dump and restore the old database (optional)

  • mysqldump -u root -pmypasswd kimaidb > kimai_dump.sql

then get the connection info for jawsdb from the database string and just use a local mysql client to upload the old data

  • mysql -h NEWHOST -u NEWUSER -pNEWPASS NEWDATABASE < kimai_dump.sql

After this, the app didn’t work anymore. I expected a Version mismatch (the lxc container I dumped from is a old) so I just ran an update on Kimai using Heroku bash

  • heroku run bash
  • bin/console kimai:update

Which showed me some table I already head from the original installation was keeping it from applying correct migrations. Connect again using mysql client and

  • drop table kimai2_invoices;

and now the update went through without a problem. The app is working and my old records are present. やったー!

Wrapping up

One thing to note is, Heroku keeps defaulting to plain http, but I could install an extension to Firefox, https everywhere, that always tries to use https, if available. Instead of installing an extension, the redirect can be achieved by the modifying the htaccess file in the public folder. there is already a redirect rule prepared but commented. It didn’t work for me, as on Heroku the load balanced does the ssl. But a small change, using different environment variable, and it’s working perfectly.

<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteCond %{HTTP:X-Forwarded-Proto} !https
    RewriteRule ^(.*)$$1 [R,L]

I also tried setting up ssl with Heroku using a custom domain and Cloudflares origin cert, but, that is only available for paid dynos. Bummer. But I could get an “ok” appname at Heroku, so it doesn’t really matter. One thing less to worry about, actually.

Also noteworthy is, there is a limit on the free database of 5mb, here’s an sql snippet to see the current usage, or just use a GUI like HeidiSQL to check the usage

    ,sp.grantee user
    ,cast(round(sum(coalesce(t.data_length + t.index_length, 0)) / 1024 / 1024, 3) as char) db_size_mb
    information_schema.schemata s
    inner join
    information_schema.tables t on s.schema_name = t.table_schema
    inner join (
                    when spi.privilege_type = 'INSERT' then 1
                    else 0
            ) has_insert
            information_schema.schema_privileges spi
        group by
    ) sp on s.schema_name = sp.table_schema
group by


Clone the project from Heroku to the development machine, add the original Source repository, pull and push.

  • heroku git:clone -a mykimai
  • git remote add -f github
  • git pull github master
  • git push heroku master

Run the Kimai2 updater in the Heroku environment.

  • heroku run bash
  • bin/console kimai:update

That’s it! easy!



Social Links