Apparently we are the type of travellers who like to do things the hard way. We found most of the information we needed to plan our journey on this travelfish article, and the relevant ferry websites. The most important piece of information that travelfish didn’t mention is that some of the boats to Jakarta take 7-12 days instead of 24-36 hours, so pay attention to the arrival date! We almost ended up on an accidental cruise of the Indonesian islands (which might have been quite nice if we had longer on our visa).
There are two routes we could find, at around US$50pp for the whole journey, the first is covered by travelfish, the second, slightly more convoluted option is the one we took:
1. Singapore Harbourfront to Pulau Batam (operated by Singapore Cruise), takes 1.5 hours.
Pulau Batam to Tanjung Priok, Jakarta (operated by Pelni), takes ~30 hours, runs about once a week
2. Tanah Merah (near Changi airport) to Tanjung Pinang on Pulau Bintan (operated bySingapore Cruise), takes 2 hours.
Then, 25km across Pulau Bintan to Kijang, which can be done by taxi from Tanjung Pinang, or by bike if you have one.
Finally, Kijang to Tanjung Priok (operated by Pelni), takes ~40 hours, runs about once a week, ticket office at the turnoff to the ferry port.
All these boats let you take bicycles onboard. Boats from Singapore charge SG$15 per bike. Pelni ferries charge nothing – just roll your bikes right in to the sleeping area. All Pelni boats include a bed and three meals a day with your ticket, though meals are small and mostly rice. Take extra water and snacks.