diff --git a/doc/dev-notes.txt b/doc/dev-notes.txt
index 0136082b..e3bc6694 100644
--- a/doc/dev-notes.txt
+++ b/doc/dev-notes.txt
@@ -95,7 +95,6 @@
 
   # Releases -> Views.
   alter table ReleaseSets rename to Views;
-  alter table Views rename column name to view_;
   alter table ReleaseSetJobs rename to ViewJobs;
   alter table ViewJobs rename column release_ to view_;
   alter table ViewJobs drop column mayFail;
@@ -129,8 +128,6 @@
 
   select * from (select project, jobset, job, system, max(timestamp) timestamp from builds where finished = 1 group by project, jobset, job, system) natural join builds;
 
-* nix-env -f . -u --leq sqlite perl-Catalyst-Engine-HTTP-Prefork perl-Catalyst-View-Download perl-DBD-SQLite perl-IO-Compress-Bzip2 perl-IPC-Run perl-Task-Catalyst-Tutorial perl-XML-Simple
-
 
 * Delete all scheduled builds that are not already building:
 
diff --git a/src/lib/Hydra/Controller/Project.pm b/src/lib/Hydra/Controller/Project.pm
index 5bdee96f..d47eb049 100644
--- a/src/lib/Hydra/Controller/Project.pm
+++ b/src/lib/Hydra/Controller/Project.pm
@@ -210,4 +210,17 @@ sub create_view : Chained('project') PathPart('create-view') Args(0) {
 }
 
 
+sub releases : Chained('project') PathPart('releases') Args(0) {
+    my ($self, $c) = @_;
+    $c->stash->{template} = 'releases.tt';
+    $c->stash->{releases} = [$c->stash->{project}->releases->all];
+}
+
+
+sub create_release : Chained('project') PathPart('releases/create') {
+    my ($self, $c) = @_;
+    $c->stash->{template} = 'create-release.tt';
+}
+
+
 1;
diff --git a/src/lib/Hydra/Schema.pm b/src/lib/Hydra/Schema.pm
index 1cd3bc72..3652bd28 100644
--- a/src/lib/Hydra/Schema.pm
+++ b/src/lib/Hydra/Schema.pm
@@ -8,8 +8,8 @@ use base 'DBIx::Class::Schema';
 __PACKAGE__->load_classes;
 
 
-# Created by DBIx::Class::Schema::Loader v0.04999_06 @ 2009-10-21 14:17:55
-# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:Fs+amiko3zHOhK97IatSgQ
+# Created by DBIx::Class::Schema::Loader v0.04999_06 @ 2009-10-21 17:40:21
+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:yd1F1Xb8MbehV6bTP6wEGQ
 
 
 # You can replace this text with custom content, and it will be preserved on regeneration
diff --git a/src/lib/Hydra/Schema/BuildInputs.pm b/src/lib/Hydra/Schema/BuildInputs.pm
index 396b47f9..9f2dea4a 100644
--- a/src/lib/Hydra/Schema/BuildInputs.pm
+++ b/src/lib/Hydra/Schema/BuildInputs.pm
@@ -103,8 +103,8 @@ __PACKAGE__->belongs_to(
 );
 
 
-# Created by DBIx::Class::Schema::Loader v0.04999_06 @ 2009-10-21 14:17:55
-# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:7blxTiVkvfdukDiXCoD+Lw
+# Created by DBIx::Class::Schema::Loader v0.04999_06 @ 2009-10-21 17:40:21
+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:IrEeOAeGZJUN3/kCMRNy5g
 
 use Hydra::Helper::Nix;
 
diff --git a/src/lib/Hydra/Schema/BuildProducts.pm b/src/lib/Hydra/Schema/BuildProducts.pm
index f68d6513..a14d6345 100644
--- a/src/lib/Hydra/Schema/BuildProducts.pm
+++ b/src/lib/Hydra/Schema/BuildProducts.pm
@@ -91,8 +91,8 @@ __PACKAGE__->set_primary_key("build", "productnr");
 __PACKAGE__->belongs_to("build", "Hydra::Schema::Builds", { id => "build" });
 
 
-# Created by DBIx::Class::Schema::Loader v0.04999_06 @ 2009-10-21 14:17:55
-# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:8wtm75jCRmcyWInKMO826g
+# Created by DBIx::Class::Schema::Loader v0.04999_06 @ 2009-10-21 17:40:21
+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:uVdvtQkCkZXqjpLhIB6OjQ
 
 
 # You can replace this text with custom content, and it will be preserved on regeneration
diff --git a/src/lib/Hydra/Schema/BuildResultInfo.pm b/src/lib/Hydra/Schema/BuildResultInfo.pm
index 8ac1697a..67ea805c 100644
--- a/src/lib/Hydra/Schema/BuildResultInfo.pm
+++ b/src/lib/Hydra/Schema/BuildResultInfo.pm
@@ -86,8 +86,8 @@ __PACKAGE__->set_primary_key("id");
 __PACKAGE__->belongs_to("id", "Hydra::Schema::Builds", { id => "id" });
 
 
-# Created by DBIx::Class::Schema::Loader v0.04999_06 @ 2009-10-21 14:17:55
-# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:3P0UuNHhdA8VfALMfwssTA
+# Created by DBIx::Class::Schema::Loader v0.04999_06 @ 2009-10-21 17:40:21
+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:HbQGfeYtXVLhEofMmXgq9A
 
 __PACKAGE__->belongs_to(
   "failedDep",
diff --git a/src/lib/Hydra/Schema/BuildSchedulingInfo.pm b/src/lib/Hydra/Schema/BuildSchedulingInfo.pm
index 23a04aa9..45727a29 100644
--- a/src/lib/Hydra/Schema/BuildSchedulingInfo.pm
+++ b/src/lib/Hydra/Schema/BuildSchedulingInfo.pm
@@ -43,8 +43,8 @@ __PACKAGE__->set_primary_key("id");
 __PACKAGE__->belongs_to("id", "Hydra::Schema::Builds", { id => "id" });
 
 
-# Created by DBIx::Class::Schema::Loader v0.04999_06 @ 2009-10-21 14:17:55
-# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:e4O2JqrJN8coHmRdjbbxHg
+# Created by DBIx::Class::Schema::Loader v0.04999_06 @ 2009-10-21 17:40:21
+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:GoOrRB+LCSskU+/lEmhtGw
 
 
 # You can replace this text with custom content, and it will be preserved on regeneration
diff --git a/src/lib/Hydra/Schema/BuildSteps.pm b/src/lib/Hydra/Schema/BuildSteps.pm
index 51e6ba33..d60983c8 100644
--- a/src/lib/Hydra/Schema/BuildSteps.pm
+++ b/src/lib/Hydra/Schema/BuildSteps.pm
@@ -91,7 +91,7 @@ __PACKAGE__->set_primary_key("build", "stepnr");
 __PACKAGE__->belongs_to("build", "Hydra::Schema::Builds", { id => "build" });
 
 
-# Created by DBIx::Class::Schema::Loader v0.04999_06 @ 2009-10-21 14:17:55
-# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:/Qdb8Y3Tqw7AOM5ique5Nw
+# Created by DBIx::Class::Schema::Loader v0.04999_06 @ 2009-10-21 17:40:21
+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:USyzZLDTSJrN+FJg29e/vA
 
 1;
diff --git a/src/lib/Hydra/Schema/Builds.pm b/src/lib/Hydra/Schema/Builds.pm
index 84fe35f0..543f89e9 100644
--- a/src/lib/Hydra/Schema/Builds.pm
+++ b/src/lib/Hydra/Schema/Builds.pm
@@ -161,10 +161,15 @@ __PACKAGE__->has_many(
   "Hydra::Schema::BuildProducts",
   { "foreign.build" => "self.id" },
 );
+__PACKAGE__->has_many(
+  "releasemembers",
+  "Hydra::Schema::ReleaseMembers",
+  { "foreign.build" => "self.id" },
+);
 
 
-# Created by DBIx::Class::Schema::Loader v0.04999_06 @ 2009-10-21 14:17:55
-# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:eJS7WCbSjwylQuAbQtB24w
+# Created by DBIx::Class::Schema::Loader v0.04999_06 @ 2009-10-21 17:40:21
+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:otgQGRHBbSwp3Tb2UBW1Xg
 
 use Hydra::Helper::Nix;
 
diff --git a/src/lib/Hydra/Schema/CachedPathInputs.pm b/src/lib/Hydra/Schema/CachedPathInputs.pm
index ff659a46..78ae3c77 100644
--- a/src/lib/Hydra/Schema/CachedPathInputs.pm
+++ b/src/lib/Hydra/Schema/CachedPathInputs.pm
@@ -47,8 +47,8 @@ __PACKAGE__->add_columns(
 __PACKAGE__->set_primary_key("srcpath", "sha256hash");
 
 
-# Created by DBIx::Class::Schema::Loader v0.04999_06 @ 2009-10-21 14:17:55
-# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:FcLeNgdEr13JAYZLxF4/tw
+# Created by DBIx::Class::Schema::Loader v0.04999_06 @ 2009-10-21 17:40:21
+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:avORhqvcxFi5m+j9g9NYGg
 
 
 # You can replace this text with custom content, and it will be preserved on regeneration
diff --git a/src/lib/Hydra/Schema/CachedSubversionInputs.pm b/src/lib/Hydra/Schema/CachedSubversionInputs.pm
index 49e19bc3..f95ee454 100644
--- a/src/lib/Hydra/Schema/CachedSubversionInputs.pm
+++ b/src/lib/Hydra/Schema/CachedSubversionInputs.pm
@@ -40,8 +40,8 @@ __PACKAGE__->add_columns(
 __PACKAGE__->set_primary_key("uri", "revision");
 
 
-# Created by DBIx::Class::Schema::Loader v0.04999_06 @ 2009-10-21 14:17:55
-# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:y14yX+UAP03N7VGrgehcLw
+# Created by DBIx::Class::Schema::Loader v0.04999_06 @ 2009-10-21 17:40:21
+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:g3iZG160pb+oYl3LeM0duA
 
 
 # You can replace this text with custom content, and it will be preserved on regeneration
diff --git a/src/lib/Hydra/Schema/Jobs.pm b/src/lib/Hydra/Schema/Jobs.pm
index 2888124f..0b36bd94 100644
--- a/src/lib/Hydra/Schema/Jobs.pm
+++ b/src/lib/Hydra/Schema/Jobs.pm
@@ -75,8 +75,8 @@ __PACKAGE__->has_many(
 );
 
 
-# Created by DBIx::Class::Schema::Loader v0.04999_06 @ 2009-10-21 14:17:55
-# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:AjrYM1rAdgYy3j0+IFFUEw
+# Created by DBIx::Class::Schema::Loader v0.04999_06 @ 2009-10-21 17:40:21
+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:SS2bc4Erxn0xyxeEndq88Q
 
 
 # You can replace this text with custom content, and it will be preserved on regeneration
diff --git a/src/lib/Hydra/Schema/JobsetInputAlts.pm b/src/lib/Hydra/Schema/JobsetInputAlts.pm
index 3adbdaf8..b29f6fe4 100644
--- a/src/lib/Hydra/Schema/JobsetInputAlts.pm
+++ b/src/lib/Hydra/Schema/JobsetInputAlts.pm
@@ -69,8 +69,8 @@ __PACKAGE__->belongs_to(
 );
 
 
-# Created by DBIx::Class::Schema::Loader v0.04999_06 @ 2009-10-21 14:17:55
-# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:o5NC1PPpGrzOZZcumFoudg
+# Created by DBIx::Class::Schema::Loader v0.04999_06 @ 2009-10-21 17:40:21
+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:ID6qQsHY59pmsJjfQKO07g
 
 
 # You can replace this text with custom content, and it will be preserved on regeneration
diff --git a/src/lib/Hydra/Schema/JobsetInputs.pm b/src/lib/Hydra/Schema/JobsetInputs.pm
index fa2d240f..510718f5 100644
--- a/src/lib/Hydra/Schema/JobsetInputs.pm
+++ b/src/lib/Hydra/Schema/JobsetInputs.pm
@@ -65,8 +65,8 @@ __PACKAGE__->has_many(
 );
 
 
-# Created by DBIx::Class::Schema::Loader v0.04999_06 @ 2009-10-21 14:17:55
-# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:7A5UkNWAqy0XuhlKvOgr6Q
+# Created by DBIx::Class::Schema::Loader v0.04999_06 @ 2009-10-21 17:40:21
+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:TwiOYM2FLwu6Vo45BLPRAA
 
 
 # You can replace this text with custom content, and it will be preserved on regeneration
diff --git a/src/lib/Hydra/Schema/Jobsets.pm b/src/lib/Hydra/Schema/Jobsets.pm
index 14ff3d13..78f4feee 100644
--- a/src/lib/Hydra/Schema/Jobsets.pm
+++ b/src/lib/Hydra/Schema/Jobsets.pm
@@ -103,8 +103,8 @@ __PACKAGE__->has_many(
 );
 
 
-# Created by DBIx::Class::Schema::Loader v0.04999_06 @ 2009-10-21 14:17:55
-# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:vaCDa6WBFdcLMG23Nlr27g
+# Created by DBIx::Class::Schema::Loader v0.04999_06 @ 2009-10-21 17:40:21
+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:EmQCVkqwPcbXYQg9g7vWpQ
 
 
 # You can replace this text with custom content, and it will be preserved on regeneration
diff --git a/src/lib/Hydra/Schema/Projects.pm b/src/lib/Hydra/Schema/Projects.pm
index 92fd2e54..7af18cdd 100644
--- a/src/lib/Hydra/Schema/Projects.pm
+++ b/src/lib/Hydra/Schema/Projects.pm
@@ -74,10 +74,20 @@ __PACKAGE__->has_many(
   "Hydra::Schema::ViewJobs",
   { "foreign.project" => "self.name" },
 );
+__PACKAGE__->has_many(
+  "releases",
+  "Hydra::Schema::Releases",
+  { "foreign.project" => "self.name" },
+);
+__PACKAGE__->has_many(
+  "releasemembers",
+  "Hydra::Schema::ReleaseMembers",
+  { "foreign.project" => "self.name" },
+);
 
 
-# Created by DBIx::Class::Schema::Loader v0.04999_06 @ 2009-10-21 14:17:55
-# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:XcrLhOH+WuO24UFulGft1w
+# Created by DBIx::Class::Schema::Loader v0.04999_06 @ 2009-10-21 17:40:21
+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:XnlHCaAVSobU9lrUhuViXQ
 
 
 # You can replace this text with custom content, and it will be preserved on regeneration
diff --git a/src/lib/Hydra/Schema/SystemTypes.pm b/src/lib/Hydra/Schema/SystemTypes.pm
index c60f5d23..fe9581e6 100644
--- a/src/lib/Hydra/Schema/SystemTypes.pm
+++ b/src/lib/Hydra/Schema/SystemTypes.pm
@@ -21,8 +21,8 @@ __PACKAGE__->add_columns(
 __PACKAGE__->set_primary_key("system");
 
 
-# Created by DBIx::Class::Schema::Loader v0.04999_06 @ 2009-10-21 14:17:55
-# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:X1voxjTNUQrn04o0zyVZ+w
+# Created by DBIx::Class::Schema::Loader v0.04999_06 @ 2009-10-21 17:40:21
+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:Qb9hjYs3pwR/hv9ds0lbuQ
 
 
 # You can replace this text with custom content, and it will be preserved on regeneration
diff --git a/src/lib/Hydra/Schema/UserRoles.pm b/src/lib/Hydra/Schema/UserRoles.pm
index da77d5fc..7cbdb85b 100644
--- a/src/lib/Hydra/Schema/UserRoles.pm
+++ b/src/lib/Hydra/Schema/UserRoles.pm
@@ -28,8 +28,8 @@ __PACKAGE__->set_primary_key("username", "role");
 __PACKAGE__->belongs_to("username", "Hydra::Schema::Users", { username => "username" });
 
 
-# Created by DBIx::Class::Schema::Loader v0.04999_06 @ 2009-10-21 14:17:55
-# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:hbm45HCbdLU4emCiCC5gyA
+# Created by DBIx::Class::Schema::Loader v0.04999_06 @ 2009-10-21 17:40:21
+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:Jte1GUXzt62VhfWrdefJow
 
 
 # You can replace this text with custom content, and it will be preserved on regeneration
diff --git a/src/lib/Hydra/Schema/Users.pm b/src/lib/Hydra/Schema/Users.pm
index 44d964c9..3157d3d1 100644
--- a/src/lib/Hydra/Schema/Users.pm
+++ b/src/lib/Hydra/Schema/Users.pm
@@ -50,8 +50,8 @@ __PACKAGE__->has_many(
 );
 
 
-# Created by DBIx::Class::Schema::Loader v0.04999_06 @ 2009-10-21 14:17:55
-# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:4Lj9iNcQhOG5VzUXkQFzkg
+# Created by DBIx::Class::Schema::Loader v0.04999_06 @ 2009-10-21 17:40:21
+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:lcMVy+i3xmpHO4TYQYXG4Q
 
 
 # You can replace this text with custom content, and it will be preserved on regeneration
diff --git a/src/lib/Hydra/Schema/ViewJobs.pm b/src/lib/Hydra/Schema/ViewJobs.pm
index 40e2d07f..a4a6cf50 100644
--- a/src/lib/Hydra/Schema/ViewJobs.pm
+++ b/src/lib/Hydra/Schema/ViewJobs.pm
@@ -66,8 +66,8 @@ __PACKAGE__->belongs_to(
 );
 
 
-# Created by DBIx::Class::Schema::Loader v0.04999_06 @ 2009-10-21 14:17:55
-# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:63nUKTtC6XdTSawDvU+oFg
+# Created by DBIx::Class::Schema::Loader v0.04999_06 @ 2009-10-21 17:40:21
+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:new5Scb0JMcSyamjKPL2BA
 
 
 # You can replace this text with custom content, and it will be preserved on regeneration
diff --git a/src/lib/Hydra/Schema/Views.pm b/src/lib/Hydra/Schema/Views.pm
index ea2d56ac..fdee69ee 100644
--- a/src/lib/Hydra/Schema/Views.pm
+++ b/src/lib/Hydra/Schema/Views.pm
@@ -42,8 +42,8 @@ __PACKAGE__->has_many(
 );
 
 
-# Created by DBIx::Class::Schema::Loader v0.04999_06 @ 2009-10-21 14:17:55
-# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:+x+gzFpHSZzVMlsjMn47UA
+# Created by DBIx::Class::Schema::Loader v0.04999_06 @ 2009-10-21 17:40:21
+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:HzIM+PxhyBLfCX6N5zFEIw
 
 
 # You can replace this text with custom content, and it will be preserved on regeneration
diff --git a/src/root/navbar.tt b/src/root/navbar.tt
index f1e343bb..18f140ed 100644
--- a/src/root/navbar.tt
+++ b/src/root/navbar.tt
@@ -48,6 +48,9 @@
       [% INCLUDE makeLink
         uri = c.uri_for(c.controller('Project').action_for('view'), [project.name])
          title = "Overview" %]
+      [% INCLUDE makeLink
+        uri = c.uri_for(c.controller('Project').action_for('releases'), [project.name])
+         title = "Releases" %]
       [% INCLUDE makeLink
         uri = c.uri_for(c.controller('Project').action_for('all'), [project.name])
         title = "All builds" %]
diff --git a/src/root/project.tt b/src/root/project.tt
index 3784f0bd..2980052d 100644
--- a/src/root/project.tt
+++ b/src/root/project.tt
@@ -98,7 +98,6 @@
 <a href="[% c.uri_for(c.controller('Project').action_for('create_jobset'), [project.name]) %]">[Create a new jobset]</a>
 
 
-
 <h2>Views</h2>
 
 [% IF views.size > 0 %]
diff --git a/src/root/releases.tt b/src/root/releases.tt
new file mode 100644
index 00000000..6426d530
--- /dev/null
+++ b/src/root/releases.tt
@@ -0,0 +1,28 @@
+[% WRAPPER layout.tt title="Releases for Project ‘$project.name’" %]
+[% PROCESS common.tt %]
+[% USE HTML %]
+
+<h1>Releases for Project <tt>[% project.name %]</tt></h1>
+
+[% IF c.user_exists %]
+<p>
+[<a href="[% c.uri_for('/project' project.name 'releases' 'create') %]">Create a release</a>]
+</p>
+[% END %]
+
+[% IF releases.size == 0 %]
+
+<p><em>This project has no releases yet.</em></p>
+
+[% ELSE %]
+
+<ul>
+  [% FOREACH release IN releases %]
+    <li><tt>[% release.name %]</tt></li>
+  [% END %]
+</ul>
+
+[% END %]
+
+
+[% END %]
diff --git a/src/sql/hydra.sql b/src/sql/hydra.sql
index 7d4b963a..7e8e8fb1 100644
--- a/src/sql/hydra.sql
+++ b/src/sql/hydra.sql
@@ -344,6 +344,35 @@ create table ViewJobs (
 );
 
 
+-- A release is a named set of builds.  The ReleaseMembers table lists
+-- the builds that constitute each release.
+create table Releases (
+    project       text not null,
+    name          text not null,
+
+    timestamp     integer not null,
+
+    description   text,
+
+    primary key   (project, name),
+    foreign key   (project) references Projects(name) on delete cascade
+);
+
+
+create table ReleaseMembers (
+    project       text not null,
+    release_      text not null,
+    build         integer not null,
+
+    description   text,
+
+    primary key   (project, release_, build),
+    foreign key   (project) references Projects(name) on delete cascade on update cascade,
+    foreign key   (project, release_) references Releases(project, name) on delete cascade on update cascade,
+    foreign key   (build) references Builds(id)
+);
+
+
 -- Some indices.
 create index IndexBuildInputsByBuild on BuildInputs(build);
 create index IndexBuildInputsByDependency on BuildInputs(dependency);