Switching to Freemium

A few weeks ago - on November 7th - I changed the price model of iOctocat to freemium. The initial download became free and it offers most of the features the full pro version has, but limited to public repositories. The pro upgrade is available as an in-app purchase and gives access to private repositories, adds support for multiple accounts and Gitub Enterprise, as well as push notifications. The upgrade costs $9.99 (or 8,99 EUR), which is just as much as before the switch.

There were four reasons for deciding to change the price model:

  1. A potentially bigger market: Seeing GitHub’s growth compared to the low number of sales I am making (more on that later), the assumption is that there is a bigger market to tackle, which I am currently not reaching.
  2. Stagnating sales: This is the main reason to face the fact something might need improvement. Of course this could be related to other things as well, but my impression from conversations was that a good percentage of people were put off by the high price (at least in App Store terms) of the app and they had no good way to find out, whether or not the investment would be worth it.
  3. The ever present lack of demo versions: I do not want to bemoan the fact that Apple does not seem to care and leaves it to each developer to find the right way to approach this*, but from the users perspective I can totally understand that trying apps is kind of the only way to verify whether or not it is worth its price.
  4. Kick it like GitHub: I just like the fact, that the new price model fits very well with GitHub offering its functionality for free for open source projects. This makes it a natural fit and was the final trigger to make me want to do the switch.

So let’s get to the interesting part: Sales figures and actual numbers.

Before

First off, the last 30 days before the switch, which can be considered a good baseline of how v2 was selling in general up to then:

  • 266 paid sales/downloads
  • 1397 EUR revenue

iOctocat paid sales before the freemium switch

As you can see this is a bumpy mixture of sales varying from 5 to 14, averaging at around 9 sales per day. It seems that deviations like that are pretty normal for apps in that range, at least that is the impression I got from talking to some fellow developers.

After

Here are the first 30 days after after the switch:

  • 217 paid sales
  • 6261 downloads
  • 1110 EUR revenue

iOctocat paid sales after the freemium switch

iOctocat paid sales after the freemium switch

The first chart shows the number of paid sales, the second one the number of downloads. On freemium launch day the app got 2009 downloads and went hockey stick from there - unfortunately in the wrong direction ;)

However, it still has about 100 downloads per day, which shows that there is a bigger market than the app was reaching before. Nevertheless the 10x growth in download numbers did not result in more sales yet - the conversion rate of the first 30 days is ~3.5%. Of course you cannot expect people that download the app just to try it to buy the pro version immediately, even if they think the app is useful to them. That is why I think of this switch as some kind of long-term investment.

Improving conversions

Right now the app now is missing out on what would have been potential customers before: People who just use public repositories and do not need any of the pro features do not have to pay anymore which might be crazy indeed. This is the point I thought about most when I tried to come up with a good pricing model. I could have charged a low amount for public repository access, but I decided that this approach would have more cons than pros…

Pros

  • I think the app is definitely valuable to people even if they are using it in an open source context.
  • This would have also prevented some frustrating one star reviews that seem to be unavoidable when you are offering stuff for free: There will always be someone who does not appreciate your work or just likes to rant in App Store reviews.

Cons

  • Even if I would have charged only 79 cents I do not think the app would have gotten a fifth of the downloads it got the free way. You just cannot beat the magic price tag “free” and giving people a no brainer opportunity to try the app outweighs the income of some potential sales in this case imho.
  • I personally prefer to pay for an app upfront and get full access immediately, maybe that is why the model of letting customers pay twice (once for basic access, then again for the pro features) just does not seem right to me.
  • With a userbase of people who are developers themselves, I know that at least some users are buying the pro upgrade even if they do not need the addtional features. This might sound romantic, but sometimes I get feedback from customers who know that their “donation” helps to ensure the future development of the app <3
  • As I mentioned before I just like the way this fits with GitHubs pricing.

However, I am still thinking about how to make an upgrade more desirable for heavy users of the free version. The features it offers without paying go far beyond a demo version and even though I do not really like to artificially limit the functionality I might cut off some features worth paying for, so that the app does not miss out on too many people who would also be willing to pay for what they get - especially with the things in mind I still want to tackle, like the iPad interface.

One more point I will definitely change: Right now the app is not really trying to upsell anyone. Once you added your account you do not get to see any possibility to upgrade, besides a shy little button on the repository lists. The only place I advertise it prominently is the accounts list, but users of the free version rarely return to that, because they can only have a single account. That was an oversight on my part or maybe just me being naive…

Conclusion

Unfortunately changing the pricing model did not turn out to be a quick win for iOctocat, but as I said I consider the move to freemium as a long-term investment and already have some ideas on how to improve the current situation. I am curious to see what kind of results the upcoming months will yield, but I do not think I will switch back to an upfront payment model whatever happens, because of the reasons I outlined in the introduction of this article.

From a users perspective I can totally see the benefit of the freemium model and with more and more apps on the App Store taking this route I can imagine the rate of people that are willing to pay upfront go down. Nevertheless I might consider cutting off some more features from the free version to give more non-pro users a reason to upgrade. I would not really like to do this and I can imagine some push back from users of the free version, but I still want to work on the app and improve it - and making some more revenue would give me better options to do just that.

Last but not least…

* Technical sidenote: Along with iOS 7 Apple gave developers a new/better way for utilizing App Store receipts (see WWDC 2013 Session 308), which makes it possible to implement various ways of demo functionality. iOctocat uses the App Store receipt to check the version the user bought initially so that customers who bought the app prior to the switch do not have to pay again. Likewise one could implement a limited period of time with full access to give the users a trial version (which I might cover in another blog post sometime…)

Three Quick Wins for Responding to User Feedback

Giving users an easy way to submit feedback and talk to the developer about the app should be an essential part of almost every app out there. I am using HockeyApp’s feedback feature, which is not only easy to integrate, but also comes with a nice interface and makes leaving comments a seamless experience for the users.

Lately I have tried some things when responding to customer feedback and had good success with that, so I wanted to share my experience with you - so here are three tips for what you should try when answering your users feedback.

Ask for a review

You might not want to do that in case the user is not satisfied with your app and is contacting you about problems she or he is experiencing. But other than that this is a very good chance to get a user to leave a review on the App Store: Just be kind and ask them for a short, one or two sentence long review. Oftentimes the positive feedback you get is ready to be recycled as an App Store review, so provide the user with a quick link to access your apps store page and you will increase your review count in no time.

Point them to your mailinglist/social media outlets

Oftentimes when people ask for a feature that is already planned or I may be working on already, I propose they subscribe to the mailinglist or follow the apps twitter/app.net account. This assumes that you are using these outlets to frequently talk about upcoming versions, stuff that is in the making or share other information that might be useful for your users. As taking action is just one or two clicks away this converts pretty well and lets your users stay in the loop.

Use a footer/signature

Depending on the type of feedback or request I get, I sometimes use the opportunity to point to my personal website or other/similar projects. The success of this is not easy to measure unless the users might let you know afterwards that they found the information they were looking for. Nevertheless, having a signature containing one or two links at the end of an email is an unobtrusive hint that might spark interest.

Bonus tip: Be quick

Sometimes people are surprised to actually get an answer to their feedback (which happens more often than I expected). I do not only answer every request I get, but also try to do it as fast as I can. It is amazing to see how even disgruntled or angry users change their minds when they are taken care of as quickly as possible. A quick answer also sets a good ground for the other tips mentioned.


All these tips take into account that the people who contact you seem to care about your app: They are loyal (and hopefully satisfied) users that take the time to let you know what they think and want to help you improve your app. I have found that those users react very positively to these pointers and some are even thankful for you letting them know where they can find out more or follow your app more closely - and if that is the case: Congratulations, you are on the right track with what you are doing.

I hope you have similar success when experimenting with these tips - let me know what you think and how your users react.

iOctocat v2 is here

Woohoo, today is shipping day! I’m very proud to announce that version 2 of iOctocat - GitHub for iPhone is now available on the App Store :)

The app started out four years ago and has seen major enhancements during the past year: The versions 1.7 and 1.8 brought a lot of improvements (new menu, multiple accounts and GitHub Enterprise support) and even big features like push notifications. Over the past months I’ve been working diligently on the app, added tons of features and improved the overall performance. Today is the day all that goodness ships and you can tell I’m really satisfied with what it looks like by the way I’m talking about it ;)

What’s new

People have sent very nice feedback over the past months, asking for features and enhancements that I now found the time for to implement. These are some of my favorites from the new release:

  • Markdown rendering in issues and comments
  • Support for emojis (including autocompletion)
  • Links in event items instead of buttons
  • Pagination for lists

Many users have asked for better issues integration. This has been on my internal list for a long time and now it is finally available. Here are the major issues additons that will help you to better manage your workflow on the go:

  • Issue search and filtering
  • Manage labels, milestones, and assignees for issues and pull requests
  • Drafts for issues, milestones and comments
  • Copy, edit and delete comments
  • Display issue/pull request events (i.e. closed, merged) alongside comments

In addition to that there are also some new niceties that have been missing before:

  • Search with options for sorting and language
  • Fullscreen in file- and webview
  • Fork a repository
  • Performance improvements for table views with cells with dynamic height
  • Massive improvements for viewing files in commits and pull requests

What’s next

I laid out my plans for the next versions in detail in a previous blog post, here is the TL;DR for the upcoming months:

  • v2.0.1 - Bugfixes (as there definitely will be rough edges I’ve overseen)
  • v2.1.0 - All about iOS 7 (and it will require the new OS version)
  • v2.1.x - New features like commenting on lines of pull request diffs and repository discovery functions via the new GitHub search API (much asked for)
  • v2.2.0 - iPad UI: Separate app with a new UI for the iPad

That’s it

Head over to the App Store and get iOctocat v2 now. If you find the time, please write a review and tell your friends, that helps a lot! And in case you got feedback: Just let me know, I’m here to listen and willing to improve the upcoming versions responding to your needs!

iOctocat: GitHub for iPhone

I am currently working on iOctocat v2.0 and because people keep asking, here are thoughts and insights I would like to share about the progress and ideas behind it.

In general

v2 will be a new app and in contrast to the current version 1 it will not be universal, which means separate apps for the iPhone and iPad.

Up to now, the iPad version of the app has been neglected and has not gotten the attention it deserves. There have been attempts to create an unique experience which utilizes the surplus of screen real estate, but those have not gotten very far, because I wanted to make the app feature complete before improving the UI. As a result I made v1 universal to please everyone who would like to use the app on the iPad, even though it is just a scaled up version of the iPhone UI. Judging from the reviews and feedback I got, this made half of the user base happy, because they liked having a single app which they paid for once and could use on all their devices. The other half was buying the app because they wanted it to work primarily on their iPad - those users felt like they did not fully get what they wanted and I can totally understand that.

To make a long story short: v2.0 will be iPhone only at first, because I still wanted to make it feature complete first. Once that is out on the App Store I will work on v2.1 to make the app ready for iOS 7. This will be a big deal and include some major UI changes I have postponed so far, because I would have to redo the UI with iOS 7 anyways. After that and having done the inevitable UI changes, I want to devote the time and energy to bring you the iPad experience of iOctocat we all have been waiting for.

E.T.A.

My initial plan was to release v2.0 alongside iOS 7 this fall. I have been able to work on the app full time for the past weeks and have made good progress by now, which led to the decision to release it earlier than that. As the app is almost feature complete with what I planned for the new version it does not make too much sense to wait for iOS 7 - in fact it would be counterproductive: Shipping v2.0 would have been too much of a big bang release with lots of new features and a complete overhaul of the UI, which in itself will take me some time to get right - even when focusing on the iPhone only at first.

Right now shipping v2.0 in mid August seems realistic and that is what I strive to do.

Another good thing about shipping independently from the new OS version is that people that do not want to upgrade to iOS 7 immediately will be able to take advantage of all the features the new app brings.

There will be time to fix bugs and maybe add one or two more features before shipping v2.1, which will target iOS 7 and leave behind support for iOS 6 and below. That is because the UI changes iOS 7 brings are so drastic that I will not be able and do not want to maintain two separate versions of the UI.

Pricing

As with v1, I need to finance working on the app - even more so now that I have already dedicated several weeks of full time work to hack on the new version.

The iPhone version will be $9.99 (8.99 EUR). I have not decided on a price point for the iPad version yet, but I think it will be a little more than that and I want to justify this by delivering a very good experience on the iPad.

Two weeks ago when I decided to ship v2.0 around mid August I cut the price of v1 in half - it is currently available on the App Store for $4.99. This makes six weeks of transitioning period and I did this to be fair to everyone buying the app from then on. I was hoping for Apple to announce paid upgrades at WWDC 2013, but it seems unlikely that we will see something like that in the near future. Having paid upgrades would have been my favorite option, but as this will not happen I think doing it this way seems like the best option, because I do not think there will be an introductory price for v2.0 and I simply cannot grant existing customers a discount.

The road so far and on

I really enjoy what I am doing right now: Hacking on an app I myself need and enjoy using, contributing to open source along the way, and talking to other developers that live the indy life. I am very grateful for the past weeks and fell in love with being able to work on what I think is best - and deciding what to do and deliver value whenever I open my MacBook or talk to customers.

Having said that, I hope you will enjoy v2 as much as I already did - WIN WIN.

App Store Pricing

Kicked off by a great series written by Michael Jurewitz (links below) a new discussion about pricing on the App Store has been spawned. Here’s my take on it, along with a few experiences.

TL;DR If you have an app on the App Store, do yourself and your fellow devs a favor, pause reading now, go to iTunes Connect and raise the price of your app(s) to what you honestly feel you deserve.

I assume there is room for improvement and that you can raise the price, because when you initially set the price you most likely fell into the trap of not being couragous enough to charge what it might be worth. Go for it – in case you are hesitant, just make it an experiment for the next two weeks. I did the same half a year ago, and I’m writing this to encourage you. Here are my findings…

A little bit of background…

All the experiences I’m talking about are based on what happened to iOctocat - GitHub for iOS within the last six months. Up to then it’s been a part time project of mine, which started four years ago and got developed further whenever I found the time and energy in my spare time. When the iPhone 5 came out I made the decision to either put it aside or develop it on a more serious basis, because the state back then was unsatisfying for both parties, customers and me: Important features were lacking and bugs took a long time to get fixed, because when you are a father of two, you got some more important things to do on your evenings and weekends.

Up to then the price was $4 (which was already high compared to the race-to-the-bottom prices we see all over the App Store) and I the income I made from that was far from covering the costs of development and support - and to be honest, somewhere along the way the cost of development neared zero, because I was just lacking the motivation to put more time into it.

So I decided to raise the price to around $10 (I experimented with variations over a couple of weeks) and see what happens: In case it takes off and I can make at least some more money off of it, I’ll start investing my time again - if not, I’ll let it sink, because there does not seem to be a market that allows sustainable development of that kind of thing anyway.

I personally did not really expect what happend, but having read so far, you might already guess the direction it went in…

More income

The amount of units sold stayed nearly the same, at first they dropped off a little – but that does not matter, because even if you double your price and only sell half the amount, you are winning: At least in theory there should be less costs for support and you validated that there still is a market for that kind of product in a higher price range. Of course this depends on the actual units sold, but you get the idea…

Then, after a while the sales started to increase. Not like they went through the roof, but the numbers improved. Don’t get me wrong: I’m not talking about wanting to become rich from the app (and even if I would, I’d be still far from it) - my point is that I wanted to find a price point that allows to develop/run/maintain the app on a sustainable basis, which you just cannot do for 89 cents as an independent developer.

iOctocat - just like most other apps - is an app with a one time fee: You pay it once and get all the updates for free. I thought about introducing other models while working on support for push notifications (that just landed btw), but I decided to keep everything as it is, because at that price point I can offer the additional services without charging an extra amount for push, which would have felt somewhat strange anyways.

Now I cover the costs by charging what I think the app is worth, which gives me the chance to also reinvest the money for things that will improve the customers experience, like services as HockeyApp, development apps like Deploymate or even giving back to people who contribute on GitHub - win win.

More loyal users

Besides the increased value on my side, it also feels like the customers value it more. Of course the perceived value automatically increases when you pay more for something, but first and foremost I am talking about that the customers tend to feel more involved in the app. I don’t think that iOctocat being open source makes it a special case, but now I even get sales and reviews that are like “Wanted to support the project, cheers!” (actual review from the App Store).

The feedback and reviews I get really improved when I increased the price. One factor for that seems to be that the higher price sieves out the people that spontaneously bought it and then were annoyed, that it does not also offer a code editor and the whole file management via Git.

Instead, customers seem to take the time to see whether or not it fits their needs and reach out to me via Twitter or ADN to ask if it supports what they expect. This way I have more contact to (potential) customers and can directly react to feedback and questions. I could go into a seperate discussion about how the current state of the App Store not allowing to answer reviews or offering trials does otherwise make this a problem in the first place, but that’s another topic.

The customers also feel like they have a say - and of course they do. I try to respond as best as I can to feature requests, issues and feedback I get, which brings me to my last point…

More motivation

Now that I can at least justify putting my spare time into the app, I really enjoy it again. I guess a look at the contributions chart on GitHub says it best…

iOctocat GitHub Contributions

There is a lot more coming and I’m really looking forward to dedicate at least one month of full time work to iOctocat this summer. The list of things I want to add and improve is still long - and hacking on the project is fun again.

So were do we go from here?

Well, we cannot sit and wait for Apple (or even expect them) to fix the pricing on the App Store. That ship has sailed and somehow we developers need to educate users and customers that running an app needs a sustainable basis that just cannot be achieved by charging 89 cents. And it already has begun, see the list of selected posts on that topic below, that were prior to this one.

At least from my experience people like to hand you their money if you make their life easier or more fun. This makes for a great conclusion too: Please don’t choose to compete on price, make quality and dedication to your product the main differentiator - your customers and fellow devs will thank you.

Further reading

UITableViewCell height with UITextView

You will find lots of resources on the internet about calculating the height of a UITableViewCell based on an inner UITextView. Most of what I’ve found when I was trying to find a solution was either incomplete or wrong. Here are my learnings and the approach I’m taking to tame what gave me headaches for some time now.

Prerequisites

I wanted this to work for both table view styles within universal apps that support autorotation. The solution should also be flexible enough to allow for subclassing and configuration of the custom table view cell so that I can reuse the code for different kinds of cells.

UITableViewCell

To calculate the height of the cell (most likely to be used in tableView: heightForRowAtIndexPath:) you have to know its width so that you can calculate the size of the UITextView with the method sizeWithFont:forWidth: lineBreakMode: provided by the NSString class. That is because the naive approach of adding the cell to the table view and asking for the frame size of the text view does not work - tableView:heightForRowAtIndexPath: gets called before tableView:cellForRowAtIndexPath: so that you have to do the calculation on your own.

UITextView

First of all we have to notice that an UITextView has an inset of 8px on each side. I have not found official documetation about this and event though its contentInset property is set to UIEdgeInsetZero by default, it has this 16px horizontal padding we have to subtract when calculating with the text views width.

Note: On StackOverflow and elsewhere you can find remarks about reduzing the padding by applying a custom inset with negative values, like this:

textView.contentInset = UIEdgeInsetsMake(-8,-8,0,0);

This indeed unsets the visual padding, but from what I have seen and tested it does not make the text run wider - you still have to subtract the 16px. With this example the text just seems to start 8px more to the left, but still wraps at the original width.

The approach

There is one thing I don’t really like about the solution I’ve found and that is passing in the table view to the method - (CGFloat)heightForTableView:(UITableView *)tableView that does the calculation. However I did not find a way to avoid this, because we need the width of the cells outer frame, because it will give us a way to get to the cells width - this way it works with autorotation and cell reuse.

The code for the custom UITableViewCell subclass (TextCell) looks like this:

@implementation TextCell

// UITextView has an inset of 8px on each side
- (CGFloat)textInset {
    return 8.0f;
}

// Sets the text and adjusts the frame height
// so that the text view does not scroll
- (void)setContentText:(NSString *)theText {
    self.textView.text = theText;
    CGRect frame = self.textView.frame;
    CGFloat cHeight = self.textView.contentSize.height;
    frame.size.height = cHeight + self.textInset;
    self.textView.frame = frame;
}

// Calculates the text views width by subtracting
// the horizontal insets and margins
- (CGFloat)textWidthForOuterWidth:(CGFloat)outerWidth {
    CGFloat textInset = self.textInset * 2;
    CGFloat marginH = self.marginLeft + self.marginRight;
    CGFloat width = outerWidth - marginH;
    CGFloat textWidth = width - textInset;
    return textWidth;
}

- (CGFloat)heightForTableView:(UITableView *)tableView {
    // calculate the outer width of the cell
    // based on the tableView style
    CGFloat outerWidth = tableView.frame.size.width;
    if (tableView.style == UITableViewStyleGrouped) {
        // the grouped table inset is 20px on
        // the iPhone and 90px on the iPad
        BOOL iPad = [UIDevice currentDevice].userInterfaceIdiom ==
          UIUserInterfaceIdiomPhone;
        outerWidth -= iPad ? 20.0f : 90.0f;
    }
    CGFloat textInset = self.textInset * 2;
    CGFloat marginV = self.marginTop + self.marginBottom;
    CGFloat textWidth = [self textWidthForOuterWidth:outerWidth];
    // use large value to avoid scrolling
    CGFloat maxHeight = 50000.0f;
    CGSize constraint = CGSizeMake(textWidth, maxHeight);
    CGSize size = [self.textView.text
        sizeWithFont:self.textView.font
        constrainedToSize:constraint
        lineBreakMode:UILineBreakModeWordWrap];
    CGFloat height = size.height + textInset + marginV;
    return height;
}

@end

You may have noticed, that the code above refers to getters for the margin. I use these to configure the coordinates that text views in different kinds of TextCell subclasses may have.

Cellconfiguration in Interface Builder

For example I would use these settings in a concrete subclass when the UITextView in Interface Builder is set like in the screenshot above.

- (CGFloat)marginTop {
    return 65.0f;
}

- (CGFloat)marginBottom {
    return 40.0f;
}

- (CGFloat)marginLeft {
    return 1.0f;
}

- (CGFloat)marginRight {
    return 1.0f;
}

The code and examples for this are taken from the iOctocat codebase.

Rails-Hosting auf Uberspace

Uberspace - Hosting on Asteroids Ich bin großer Fan der einladenden Atmosphäre, Einfachheit und dem großartigen Support auf Uberspace. Konsequenterweise hab ich nun auch meine letzten privaten Projekte dorthin umgezogen und mich dabei auch mit dem Rails-Deployment auf Uberspace auseinander gesetzt.

Für die ersten Schritte findet man im Wiki eine Anleitung mit allen generellen Einzelheiten zum Aufsetzen einer Rails-Anwendung auf Uberspace. Der dort beschriebene Ansatz setzt der Einfachheit halber auf das manuelle Ausführen der einzelnen Schritte und das Ausliefern der Anwendung über FastCGI. Da das nicht unbedingt etwas ist, was man aus dem täglichen Rails-Kontext kennt, hier ein paar Hinweise und Learnings zur Automatisierung des Deployments und zur Verwendung alternativer Applikationsserver.

Automatisierung des Deployments

Wer seine Anwendungen mit Capistrano deployed, sollte einen Blick auf uberspacify werfen, ein Gem das alle nötigen Capistrano-Scripte mitbringt, um den eigenen Uberspace startklar für das Rails-Hosting zu machen. Als Fan von Vlad habe ich mich von uberspacify inspirieren lassen und die nötigen Uberspace-Tasks für Vlad geschrieben. Das Script enthält die wichtigsten Anmerkungen und Konfigurationshinweise, hier noch ein paar generelle Dinge:

Die von Vlad/Capistrano benutzte Shell ist nicht die interaktive Shell, die man selbst beim Login über SSH bekommt. Um sicherzustellen, dass auch diese Tools die gleiche Konfiguration (bspw. den Pfad zu einer angepassten Ruby-Version) haben, sollten die Anweisungen dafür in die .bashrc statt in die .bash_profile geschrieben werden.

Die Anwendung selbst muss unterhalb /var/www/virtual/username/ liegen, damit der Apache darauf zugreifen kann. Im einfachsten Fall würde man einfach in das html-Verzeichnis dort deployen, was aber mit der von den Deployment-Tools generierten Struktur (current, releases, shared) ungünstig ist, da diese dann im DocumentRoot des Webservers liegen. Ich mache es daher so, die Anwendung in /var/www/virtual/username/rails/myapp zu deployen und einen Symlink mit dem Hostnamen der Domain auf das darin liegende current/public zu setzen. Damit funktioniert dann auch der Zugriff aus die Assets der Anwendung problemlos, ohne weitere Einstellungen in der .htaccess machen zu müssen. Im Zusammenhang mit den so verwendeten Domains sollte man den Hinweis zum angepassten DocumentRoot beachten.

Alternative Applikationsserver

Statt FastCGI benutze ich Thin, generell treffen die nachfolgenden Dinge aber auch zu, sollte man sich für die Verwendung von Unicorn, Puma, Mongrel oder Passenger Standalone entscheiden. Wichtig ist eigentlich nur, dass man den Applikationsserver auf hohen Port laufen lässt und ihn mittels Daemontools überwacht, so dass er auch unabhängig von Webserver-Restarts persistent läuft.

Das run-Script dafür, sieht dann beispielsweise folgendermaßen aus:

#!/bin/bash
export HOME=/home/username
source $HOME/.bash_profile
cd /var/www/virtual/username/rails/myapp/current
exec /home/username/.gem/ruby/1.9.1/bin/bundle exec thin start -p 43862 -e production 2>&1

Zu guter letzt benötigt man unter current/public noch eine .htaccess-Datei, die eine RewriteRule enthält, um die Requests an den Applikationsserver-Proxy weiterzuleiten. Im einfachsten Fall reicht folgendes:

RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ http://localhost:43862/$1 [P]

Darüber hinaus gibt es hier auch noch eine Variante der .htaccess mit Asset-Caching und Wartungsmodus.

Sowohl uberspacify als auch das Vlad-Script kümmern sich darum diese Dateien entsprechend der gegebenen Konfiguration zu generieren. Sollte jemand das Vlad-Script ausprobieren oder Anmerkungen zu den hier gegebenen Tipps haben freue ich mich natürlich über Feedback dazu :)

Bitte beachten!

Uberspace hat ein SSH-Connection Limit, was auch gut so ist. Vlad wiederum öffnet für jedes Kommando eine neue SSH-Verbindung, so dass man schnell mit dem Connection Limit kollidiert. Daher ist es sinnvoll, SSH-Multiplexing zu aktivieren, was man mit folgenden Zeilen in der ~/.ssh/config tun kann:

Host *
  ControlMaster auto
  ControlPath /tmp/ssh-master-%r@%h:%p
  ControlPersist 15m

Dies führt dazu, dass bei jedem weiteren Befehl die bereits bestehende SSH-Verbindung genutzt wird. Man hat somit auch gleich ein schnelleres Deployment, daher wird auch in der Vlad FAQ generell dazu geraten, SSH-Multiplexing zu verwenden.

Ploppcasts

Ploppcasts Zusammen mit den Hamburger Kollegen Daniel Harrington, Jan Krutisch und Peter Schröder sitze ich nun ein bis zweiwöchentlich Donnerstag abends virtuell beisammen, um Ploppcasts aufzunehmen: Unser Beitrag zu deutschsprachigem Podcasting mit Themenschwerpunkt rund um alles was mit Ruby, Rails, Webentwicklung und Freelancertum zu tun hat. Zu finden ist das Resultat dann in iTunes, bzw. lässt sich auch mit jeden anderen Feedreader abonnieren. Über Feedback und Bewertungen in iTunes freuen wir uns natürlich riesig!

Setup

Da ich auf der Suche nach einem geeigneten Setup zur Aufnahme bis auf einen Post auf MacWorld nur wenig gefunden habe, hier auch noch ein paar Worte zu unserem Setup: Wir treffen uns in Skype zu einem Gruppengespräch und nehmen mit dem CallRecorder die jeweiligen Tonspuren auf. CallRecorder beinhaltet mehrere Anwendungen, unter anderem auch eine, um die einzelnen Aufnahmen in zwei Tonspuren aufzuteilen: Die eigene und die der anderen Gesprächsteilnehmer.

Die Einzelspuren werden dann in GarageBand übernommen und mit einer der Gesamtspuren abgeglichen, so dass alle Teilnehmerspuren am Ende synchron sind. In GarageBand lässt sich auf der Rest eines Podcasts ganz gut produzieren, wie beispielsweise Bilder und Shownotes einpflegen. Wer es richtig rund mag, könnte auch noch endlos abgehen und Versprecher etc. rausschneiden, das ist uns aber ehrlich gesagt zu viel Aufwand und wir hoffen, dass wir uns mit der Zeit was solche Dinge angeht einfach genügend verbessern, um die Ähs und Öhs zu reduzieren ;)

Generell ist es ratsam, die Aufnahme mit einem vernünftigen externen Mikrofon zu machen, wie beispielsweise dem Go Mic von Samson. Das interne MacBook Mikrofon geht zwar auch, ist aber eben auch anfälliger für Störgeräusche die durch’s nebenbei tippen oder das Rauschen der Festplatte entstehen. Absolut wichtig ist es, dass jede der aufnehmenden Parteien Kopfhörer auf haben, ansonsten nimmt die eigene Spur auch das mit auf, was man selbst von den anderen empfängt.

Wer sich auskennt, darf uns gerne wissen lassen, wie wir die Aufnahme darüber hinaus noch verbessern könnten!

Danke

Vielen Dank an dieser Stelle auch an SoundCloud, die uns auf ihrer wunderbaren Plattform beherbergen und uns den Speicherplatz sponsorn. Außerdem geht ein dickes Dankeschön an Benjamin Rabe, der uns das schnieke Logo gefingermalt hat. Wer uns folge möchte, findet und natürlich auch auf Twitter und Facebook.

Deployment with Vlad

When I ask people about how they deploy their Rails apps, most of them answer that they are using good old Capistrano. It is the most popular solution because it has been around since almost when people started deploying Rails applications and it works well in most cases. However, if you want to do a lot of custom tasks and need to look under the hood to extend it, Capistrano seems somewhat complicated. At least it did at the time I left it in favor of Vlad - an imho more intuitive deployment tool.

Though one might not like the offense against Capistrano that was shown when Vlad was released in 2007, it addresses some valid points when arguing against needless complexity. But enough of the history, this was almost five years ago, both of the tools have improved, made their way and all I wanted to share is some snippets and insights I got from using Vlad over the last years.

The main thing I like abot Vlad is that it is based on Rake, which makes it so easy to learn, understand and extend. If you want to do something custom, all you have to do is to write a Rake task. And as you will want to use them across different projects, you can even bundle them up and distribute them as a gem, like I’ve done with vlad-extras, a set of extensions for tools like DelayedJob, ThinkingSphinx, Monit, Whenever and so on.

Here is what a simple config/deploy.rb file might look like:

# Custom variables used in the following config
set :application, "appname"

# Required variables
set :domain, "#{application}.com"
set :deploy_to, "/var/www/#{application}"
set :repository, "git@github.com:myaccount/#{application}.git"

# Optional variables
set :user, "deploy" # if different from your current login
set :bundle_cmd, "/usr/local/bin/bundle"
set :rake_cmd, "#{bundle_cmd} exec rake"
set :revision, "origin/master"

# vlad-extras config
set :copy_shared, {
  'config/maintenance.html' => 'config/maintenance.html',
  'config/database.yml'     => 'config/database.yml' }
set :symlinks, {
  'assets'              => 'public/assets',
  'config/database.yml' => 'config/database.yml' }
set :deploy_tasks, %w(
  vlad:maintenance:on
  vlad:update
  vlad:symlink
  vlad:bundle:install
  vlad:assets:precompile
  vlad:migrate
  vlad:start_app
  vlad:maintenance:off
  vlad:cleanup)

# Bundler ships with vlad integration you'll have to require
# in order to use the vlad:bundle tasks
require 'bundler/vlad'

# Require custom vlad tasks or recipes from vlad-extras
require 'vlad/maintenance'

# Some custom tasks, can be included directly in the deploy file
namespace :vlad do
  namespace :custom_script do
    %w(start stop restart).each do |task|
      desc "#{task.capitalize} the custom script"
      remote_task task, :roles => :app do
        run "cd #{current_release}; RAILS_ENV=#{rails_env}
          #{bundle_cmd} exec ruby script/custom #{task}"
      end
    end
  end
end

Here is an example of how easy it is for instance to add staging support:

task :staging do
  set :rails_env, "staging"
  set :deploy_to, "/var/www/#{application}-#{rails_env}"
end

task :production do
  set :rails_env, "production"
  set :deploy_to, "/var/www/#{application}-#{rails_env}"
end

All you need to do is to change some variables based on the environment that is set with a simple Rake task. You’d invoke it by running

$ bundle exec rake staging vlad:deploy

Gemfile

You can include Vlad and it’s requirements in the development group, because it you do not need it in any other environment. Requiring is done in the Rakefile like shown below.

group :development do
  # Deployment
  gem 'vlad', :require => false
  gem 'vlad-git', :require => false
  gem 'vlad-extras', :require => false
end

Rakefile

Add this snippet to your Rakefile, just before MyApp::Application.load_tasks

if Rails.env.development?
  begin
    require 'vlad'
    require 'vlad-extras'
    Vlad.load(scm: :git, web: :nginx, app: :passenger, type: :rails)
  rescue LoadError
    puts 'Could not load Vlad'
  end
end

You might have to configure the options given to Vlad.load.

If you want to get started, take a look at the documentation to find out about the parts that this post leaves out.

Troubleshooting

This part is about some trouble I’ve had and it is not really Vlad specific. You might encounter these problems when trying to deploy to your production server and maybe these tips and links will help.

The deployment shell environment

If Vlad does not find some commands, try the following:

To enable per user PATH environments for ssh logins you need to add to your sshd_config:

PermitUserEnvironment yes

After that, restart sshd!

Then in your users ssh home directory (~/.ssh/environment), add something to this effect (your mileage will vary):

PATH=/opt/ruby-1.9.3/bin:/usr/local/bin:/bin:/usr/bin

For details on that, see this article on setting the deployment shell environment

SSH Agent Forwarding

Maybe you also need to configure SSH Agent Forwarding:

$ ssh-add ~/.ssh/<private_keyname>

Edit your ~/.ssh/config file and add something like this:

Host <name>
  HostName <ip or host>
  User <username>
  IdentityFile ~/.ssh/<filename>
  ForwardAgent yes

For details on that see this article on SSH Agent Forwarding

Seeds for different environments

Here’s a little Rails tip for splitting up seed data for various environments: Create the folder db/seeds/. For each environment you want to seed put a file in there named after the env. Your general seeds are put into db/seeds/all.rb. Here’s what it might look like:

|___seeds
| |___all.rb
| |___development.rb
| |___staging.rb
| |___production.rb
|___seeds.rb

Change the content of your db/seeds.rb to something like this:

['all', Rails.env].each do |seed|
  seed_file = "#{Rails.root}/db/seeds/#{seed}.rb"
  if File.exists?(seed_file)
    puts "*** Loading #{seed} seed data"
    require seed_file
  end
end

Running rake db:seed will now load up the seed data defined in db/seeds/all.rb and the current environment.

Configuring Nginx for the Asset Pipeline

Following up on the series of posts about the Rails Asset Pipeline, here are some things to keep in mind when you are using Nginx as your webserver.

Installation via Passenger

When you are installing Nginx automatically with the script that comes with Passenger, you will not be able to serve the gzipped file variants that get precompiled. That’s because Passenger currently does not configure Nginx with the http_gzip_static_module that would be necessary for including the directive gzip_static on. I’ve opened a pull request for that, but for now you’ll have to choose the manual installation, if that’s what you are after - and why wouldn’t you?

# download and untar nginx
wget -O /tmp/nginx.tar.gz http://www.nginx.org/download/nginx-1.0.10.tar.gz
tar xzvf /tmp/nginx.tar.gz
rm /tmp/nginx.tar.gz

# install nginx via passenger install script, already includes
# --with-http_ssl_module
# --add-module for passenger
passenger-install-nginx-module --auto --prefix='/opt/nginx' --nginx-source-dir='/tmp/nginx-1.0.10' --extra-configure-flags="--with-http_gzip_static_module"

You might want to change the version and prefix to fit your needs. Like mentioned in the comments you don’t have to take care of including the passenger or ssl module, because the installation script adds them automatically.

Nginx configuration

This is already part of the Asset Pipeline Guide, but I’ll mention it here again, so that this article is complete - here are the directives for the assets location config:

location ~ ^/(assets)/  {
  root /path/to/site;
  gzip_static on;
  expires max;
  add_header Cache-Control public;
  # access_log /dev/null;
}

I’ve also disabled access logging, so that asset requests do not clutter up the log.

Mathias brought up a good point, why one might not want to do that.

Sendfile Header

In your app’s production.rb you should uncomment to following line, to let Nginx send static files:

config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect'

Verify that it’s working

After deploying the changes, you can check if Nginx is now serving the gzipped compressed versions of your asset files. For that you can use tools like this HTTP Compression Tester and enter the address of your application.js and application.css

Ältere Artikel im Archiv