Provisioning Content Type's fields is quite straight forward when using Solutions: using Element Manifests you can define your own Content Types and their properties among which fields. Unfortunately there is one serious con to the solution Microsoft has offered: you simply cannot provision any Lookup Field using the Element Manifest.

Lookup Fields obtain their values from an existing list. Each Lookup Field is being linked to its list using the list's ID. As the ID's are being generated after creating the instances there is no way to provision a Lookup Field linked to a newly created list during Solution deployment. That's when Feature Receivers are useful. Feature Receivers are nothing more than custom code you can attach to events triggered by a feature. Provisioning a custom field programmatically works exactly the same as doing it using the GUI: first you need to create a Site Column and then you need to attach it to the Content Type of your choice.

Assuming you have a solution with a feature called ContentDefinition responsible for deploying List Templates, provisioning List Instances and creating Site Columns and Content Types. It would look something close to:

<Feature Id="GUID"
    Title="Content Definition"
    Description="Configures content definition"
    Version="1.0.0.0"
    Scope="Site"
    Hidden="FALSE"
    xmlns="http://schemas.microsoft.com/sharepoint/">
  <ElementManifests>
    <ElementManifest Location="SiteColumns\SiteColumns.xml"/>
    <ElementManifest Location="ContentTypes\ContentTypes.xml"/>

    <!-- Lists -->

    <!-- Custom List -->
    <ElementFile Location="CustomList\schema.xml"/>
    <ElementFile Location="CustomList\AllItems.aspx"/>
    <ElementFile Location="CustomList\DispForm.aspx"/>
    <ElementFile Location="CustomList\EditForm.aspx"/>
    <ElementFile Location="CustomList\NewForm.aspx"/>
    <ElementManifest Location="CustomList\Elements.xml"/>
  </ElementManifests>
</Feature>

First of all you provision your Site Columns and Content Types. Then you deploy a custom List Definition and using Elements.xml you create a new instance of that list.

To make use of Feature Receivers you need to extend the Feature element with two attributes: ReceiverClass en ReceiverAssembly:

<Feature Id="GUID"
    Title="Content Definition"
    Description="Configures content definition"
    Version="1.0.0.0"
    Scope="Site"
    Hidden="FALSE"
    xmlns="http://schemas.microsoft.com/sharepoint/"
    ReceiverAssembly="Assembly, Version=1.0.0.0, Culture=neutral,
    PublicKeyToken=keytoken"
    ReceiverClass="Assembly.ContentDefinitionFeatureReceiver">

Now you can proceed to create the ContentDefinitionFeatureReceiver class. First of all inherit from the Microsoft.SharePoint.SPFeatureReceiver class. Then override the event you want to use: as for Fields provisioning you should use the FeatureActivated synchronous event.

As I have already mentioned you need to complete two steps in order to provision a Lookup Field: you have to create a new Site Column and then add it to the desired Content Type. The best approach is to create separate methods to complete both tasks as you might need to provision multiple Lookup Fields.

public static SPFieldLookup CreateLookupField(string fieldName,
  string group, bool required, bool allowMultipleValues, SPWeb w,
  SPList lookupList, string lookupField)
{
  w.Fields.AddLookup(fieldName, lookupList.ID, lookupList.ParentWeb.ID, required);
  SPFieldLookup lookup = (SPFieldLookup)w.Fields[fieldName];
  lookup.AllowMultipleValues = allowMultipleValues;
  lookup.LookupField = lookupField;
  lookup.Group = group;
  lookup.Update(true);
  return lookup;
}

You can use this method like this:

SPFieldLookup lookup = CreateLookupField("CustomLookup",
  "Custom fields", false, false, SPContext.Current.Site.RootWeb,
  SPContext.Current.Site.AllWebs["MyWeb"].Lists["CustomList"],
  "Title");

The next step is to link the created Lookup Field to the existing Content Type:

public static void LinkFieldToContentType(SPWeb web,
  string contentType, SPField field)
{
  SPContentType ct = web.ContentTypes[contentType];
  ct.FieldLinks.Add(new SPFieldLink(field));
  ct.Update();
}

As we have just created a method returning the created Lookup Field all we have to do is to call the LinkFieldToContentType method with the created field passed as parameter:

LinkFieldToContentType(SPContext.Current.Site.RootWeb,
  "MyContentType", (SPField)lookup);

It is important you pass the root web as the web parameter: it's the web containing the definitions of your Content Types.

And that's all. As both of the methods are static you could incorporate them in a Utility class and make use of them in any of your receiver classes.