IgnisDa's Blog

Self hosting Wakatime

Tutorial Metrics Self Hosting

Introduction

This article will show you how to properly deploy the Wakapi project on your dokku server.

Deployment strategy

Deploying to dokku using the default buildpack strategy is not possible as the project needs some custom configuration. So we will use the Dockerfile Deployment strategy.

Steps

The following are the steps needed to deploy the project.

Create the dokku app and add services

We will create a dokku app and the various services it needs.

dokku apps:create wakapi
dokku mysql:create wakapi # you can use any database you want
dokku mysql:link wakapi wakapi
dokku domains:set wakapi wakapi.example.com
# do this only if the dockerfile is not in the root of the repository
dokku builder-dockerfile:set wakapi dockerfile-path 'apps/wakapi/Dockerfile'

Create the required files

I normally keep all my self hosted projects in a single repository. So I will create apps/wakapi folder in my repository.

The following files are required:

  • apps/wakapi/Dockerfile: The dockerfile that is reponsible for building the image.

    FROM ghcr.io/muety/wakapi:latest as builder
    
    FROM stephenc/envsub as deps
    
    FROM alpine:3 as runner
    RUN apk add ca-certificates curl tar ca-certificates tzdata
    # You might need to change the architecture to match your server
    RUN curl -L "https://github.com/IgnisDa/rust-libs/releases/download/v0.3.0%2Bconnection-string-parser/connection-string-parser-x86_64-unknown-linux-musl.tar.gz" --output csp.tar.gz ;\
        tar -xvf csp.tar.gz ;\
        mv connection-string-parser /bin/csp ;\
        csp --version ;\
        rm csp.tar.gz
    COPY --from=builder /app/wakapi /bin/wakapi
    COPY --from=deps /bin/envsub /bin/envsub
    COPY apps/wakapi/* /
    CMD /set-env-and-start.sh wakapi -config /config/config.yml
  • apps/wakapi/config.yml: Next we have the wakapi config file.

    env: 'production'
    
    server:
      listen_ipv4: 0.0.0.0 # leave blank to disable ipv4
      listen_ipv6: # leave blank to disable ipv6
      tls_cert_path: # leave blank to not use https
      tls_key_path: # leave blank to not use https
      port: ${PORT:-5000}
      base_path: /
      public_url: '${SERVER_PUBLIC_URL}' # required for links (e.g. password reset) in e-mail
    
    app:
      import_batch_size: 200
      heartbeat_max_age: '4320h'
      data_retention_months: -1
      avatar_url_template: api/avatar/{username_hash}.svg
    
    db:
      dialect: '${DATABASE_DIALECT}'
      user: '${DATABASE_USER}'
      password: '${DATABASE_PASSWORD}'
      host: '${DATABASE_HOST}'
      port: ${DATABASE_PORT}
      name: '${DATABASE_NAME}'
    
    security:
      password_salt: '${SECURITY_PASSWORD_SALT}'
      insecure_cookies: true
      cookie_max_age: 172800
      allow_signup: ${SECURITY_ALLOW_SIGNUP:-false}
      expose_metrics: false
    
    sentry:
      dsn: # leave blank to disable sentry integration
    
    mail:
      enabled: true
      provider: smtp
      sender: '${MAIL_SENDER}'
    
      smtp:
        host: '${SMTP_HOST}'
        port: ${SMTP_PORT}
        username: '${SMTP_USERNAME}'
        password: '${SMTP_PASSWORD}'
  • apps/wakapi/set-env-and-start.sh: This is the script that will be used to set the environment variables and start the service.

    #!/usr/bin/env sh
    
    export DATABASE_USER="$(csp $DATABASE_URL --part user)"
    export DATABASE_PASSWORD="$(csp $DATABASE_URL --part password)"
    export DATABASE_HOST="$(csp $DATABASE_URL --part host)"
    export DATABASE_PORT="$(csp $DATABASE_URL --part port)"
    export DATABASE_NAME="$(csp $DATABASE_URL --part path)"
    export DATABASE_DIALECT="$(csp $DATABASE_URL --part scheme)"
    
    mkdir -p config
    
    (cat config.yml) | envsub --greedy-defaults > config/config.yml
    # delete the config file with placeholders
    rm config.yml
    
    echo "Wrote configuration..."
    
    echo "Starting service..."
    exec $@

    Remember to make the script executable using chmod +x apps/wakapi/set-env-and-start.sh.

  • apps/wakapi/CHECKS: Optionally, a CHECKS file for zero downtime deploys.

    TIMEOUT=10
    ATTEMPTS=5
    WAIT=5
    
    /api/health

Some notable things

Most of the above configuration is pretty simple, but here are some things to note.

  • We use envsub to replace the environment variables in the config file during runtime.
  • We use connection-string-parser to parse the database url into the various parts.
  • We do not bake the configuration file with secrets into the image itself, this allows us to publish this image to a public registry without worrying about leaking secrets.

Set the environment variables

As you can see in the config file above, we have a lot of environment variables that need to be set. We can set them using the dokku config:set command.

dokku config:set wakapi \
  SERVER_PUBLIC_URL="https://wakapi.example.com" \
  SECURITY_PASSWORD_SALT="supersecret" \
  MAIL_SENDER="Wakapi <my@email.com>" \
  SMTP_HOST="smtp.example.com" \
  SMTP_PORT="587" \
  SMTP_USERNAME="username" \
  SMTP_PASSWORD="password"

Make sure you replace the values with your own.

Push the app

Next, we will make a commit and push the app to dokku.

git add .
git commit -m "Add wakapi configuration"
git push dokku main

Enable SSL

Once the deployment is successful, we can enable SSL using the letsencrypt plugin.

dokku letsencrypt:enable wakapi

Bonus

You can check the configuration file that was generated by entering the running container:

dokku enter wakapi web sh
# you are now inside the shell of the running container
cat "config/config.yml"

Client setup

After the self hosted instance is up and running, you can following these instructions to set up your client.

Conclusion

It is pretty straightforward to self host Wakatime. We used some tools that made it much easier. Be sure to visit their repositories and give them a star.