From Legacy CMS to a Modern Content Platform: Migrating from CamaleonCMS to Contentful
CamaleonCMS is a Rails-based CMS that made sense when it was chosen — it integrates natively with Rails, keeps everything in the same codebase, and avoids adding external dependencies. As content requirements grow, these same properties become liabilities: the CMS is coupled to the application, content previews require a running Rails server, non-technical editors are working in an interface not designed for them.
The migration to Contentful is a common next step. This post covers the pieces that aren't obvious from the documentation: data modeling decisions, the content migration script, and the live-site rollout strategy.
Step 1: Audit the existing content model
Before touching Contentful, map what you actually have in CamaleonCMS. The common surprise: the content model in the CMS doesn't match how the content is actually rendered. CamaleonCMS uses a flexible custom field system, and over time those fields accumulate. Some are used in templates, some aren't. Some were renamed but the old data persists.
The audit output should be: for each content type, the fields that are actively rendered, their types, any validation rules, and a representative sample of existing content. This becomes the spec for your Contentful content type definitions.
Step 2: Design the Contentful content model
Resist the temptation to mirror the CamaleonCMS model exactly. A migration is an opportunity to clean up content structure that has drifted from its original intent.
Contentful-specific considerations:
- References vs. embedded content — Contentful's linked entries are powerful but add API call complexity. For content that's always fetched together, embedding is simpler.
- Localization — if you need multi-language support, Contentful's localization is first-class. Design for it from the start if there's any chance you'll need it.
- Rich text vs. plain text vs. markdown — Contentful's rich text field is powerful but has a learning curve for editors. Plain text + markdown is simpler and renders predictably. Choose based on your editors' technical comfort.
- Required fields — be conservative with required fields initially. You can tighten validation after migration; you can't loosen it retroactively without breaking existing entries.
Step 3: The migration script
Contentful has a migration SDK that handles schema changes. For content migration, you'll write a script that reads from CamaleonCMS and writes to the Contentful Management API.
require 'contentful/management'
require 'camaleon_cms' # or query the DB directly
client = Contentful::Management::Client.new(ENV['CONTENTFUL_MANAGEMENT_TOKEN'])
space = client.spaces.find(ENV['CONTENTFUL_SPACE_ID'])
environment = space.environments.find('master')
# Get the content type
post_type = environment.content_types.find('blogPost')
CamaleonCms::Post.published.each do |post|
entry = environment.entries.create(
post_type,
title: { 'en-US' => post.title },
slug: { 'en-US' => post.slug },
body: { 'en-US' => post.content },
publishedAt: { 'en-US' => post.published_at }
)
entry.publish
puts "Migrated: #{post.slug}"
rescue => e
puts "Failed: #{post.slug} — #{e.message}"
end Run the script in batches. The Contentful Management API has rate limits (7 requests/second on the free tier, higher on paid plans). Add retry logic and exponential backoff.
Step 4: Dual-write during cutover
The rollout strategy that kept the site live: dual-write.
- Run the initial migration to get all existing content into Contentful
- Implement a Contentful adapter in the Rails app that reads from Contentful
- Add a feature flag:
CONTENTFUL_ENABLED=true/false - Deploy with the flag off — the app still reads from CamaleonCMS
- Enable the flag for a single content type at a time, verify in staging, then production
- Keep CamaleonCMS writes active and run a sync job to push new content to Contentful during the transition
- Once all content types are validated on Contentful, disable the CamaleonCMS paths
The sync job is the most fragile piece. Write it defensively — log every sync, check for content drift, and run it on a short interval (5 minutes) during the transition period.
Step 5: Cleaning up
Once all traffic is serving from Contentful and the CamaleonCMS paths are disabled, there are a few remaining steps:
- Remove CamaleonCMS from the Gemfile and drop the associated tables
- Archive the migration scripts — you won't need them again, but they're useful as documentation
- Update the content editing workflow documentation for your team
- Set up Contentful webhooks to invalidate cache or trigger rebuilds on content publish
The honest assessment
This migration takes more time than it looks like. The content model design and the migration script are straightforward. The hard part is the organizational work: training editors on the new interface, migrating rich text content that doesn't map cleanly from CamaleonCMS's format to Contentful's, and handling the inevitable edge cases in 3-year-old content that doesn't fit the new model.
Budget at least as much time for the human side of the migration as the technical side.