Two Seeding Paths
The CMS needs to work in two modes: a static demo (for the IONOS VPS and local dev) and an AI-generated deployment (for AWS). The seeder checks for a GENERATED_CONTENT_B64 environment variable to decide which path to take.
public function run(): void
{
$b64 = env('GENERATED_CONTENT_B64');
if ($b64) {
$this->seedGeneratedPosts($b64);
return;
}
$this->seedBlogTypes();
$this->seedPages();
$this->seedDemoPosts();
}
Passing JSON via Base64
The content generator in the Django CMS manager produces a JSON blob. Before deploying, it's base64-encoded and passed as an environment variable — Docker Compose and ECS task definitions both support this pattern cleanly.
# In the Django CMS manager (Python):
import base64, json
content = generate_with_images(site)
b64 = base64.b64encode(json.dumps(content).encode()).decode()
# b64 is then set as GENERATED_CONTENT_B64 in the ECS task definition
firstOrCreate vs updateOrCreate
Demo posts use firstOrCreate so re-seeding doesn't overwrite manual edits. Generated posts use updateOrCreate because the content comes from the AI pipeline and should always reflect the latest generation.
// Demo — preserve manual edits
Post::firstOrCreate(['slug' => $p['slug']], $p);
// Generated — always apply latest AI output
Post::updateOrCreate(['slug' => $postData['slug']], [...]);
The Entrypoint
The Docker entrypoint runs migrations and seeds on every container start. This means a fresh ECS task always has up-to-date content without needing a separate migration step.
#!/bin/bash
php artisan storage:link --force
php artisan migrate --force
php artisan db:seed --force
php-fpm
php artisan storage:link --force creates the public/storage symlink needed to serve locally-uploaded files. Add --force so it doesn't fail if the symlink already exists from a previous container run.