To clarify, you don't need a Primary Key when using LookupRows or LookupOrderedRows, but you do need a column in your data to match rows against (which has to be an exact match). This is a little frustrating, but the workaround is simple enough.
Create a new field in your DE named 'Display'. Make this a nullable field with a boolean type and a default value of 'True'. Then use the following code in your landing page, replacing PromotionsDE
with the name of your Data Extension:
<!DOCTYPE html>
<html>
<head>
<style>
table {
font-family: arial, sans-serif;
border-collapse: collapse;
width: 400px;
}
td, th {
font-family: arial, sans-serif;
border: 1px solid #dddddd;
text-align: left;
padding: 8px;
}
tr:nth-child(even) {
background-color: #dddddd;
}
</style>
</head>
<body>
<table>
<tr>
<th>Row</th>
<th>Promo Code</th>
<th>Description</th>
</tr>
%%[
var @rows, @rowcount
set @rows = LookupRows('PromotionsDE','Display', 1)
set @rowcount = RowCount(@rows)
if @rowcount > 0 then
var @i
for @i = 1 to @rowcount do
var @promocode, @description, @row
set @row = Row(@rows,@i)
set @promocode = Field(@row,"PromoCode")
set @description = Field(@row,"Description")
]%%
<tr>
<td>%%=v(@i)=%%</td>
<td>%%=v(@promocode)=%%</td>
<td>%%=v(@description)=%%</td>
</tr>
%%[ next @i ]%%
%%[ else ]%%
<tr><td colspan="3">No rows found.</td></tr>
%%[ endif ]%%
</table>
</body>
</html>
This page will render the following HTML:

FYI, if you ever have a need to hide specific DE rows from appearing in the table, then you can just add the 'Display' field to false
.