Here you may want to be keep in mind Knuth’s warning: “premature optimization is the root of all evil”. That is, you may decide something based on limited analysis influenced also by comments in an online forum of readers with no direct experience with this code base. You can come to realize later the design is suboptimal and deal with the adverse impact if this code is of top importance to you.
But if you are not that concerned (e.g., the code may be a small side project of yours with only you as the user), then you can proceed as you think with derived type components of POINTER attribute. One consideration will be to see if you can avoid a reference to such pointer components directly in an ALLOCATE statement i.e., use the pointer components as aliases only. This may need some change in your program.
Note all that an ALLOCATE statement does with pointer objects is allocate an anonymous object of TARGET attribute and set the pointer to have that target. Such objects can easily be left dangling.
Instead consider working with a named object of ALLOCATABLE attribute and alias the pointers to said object.
type :: surface
..
end type
type :: container
type(surface), pointer :: wall => null()
type(wall_function) :: b_layer
end type
type :: wall_function
type(surface), pointer :: wall => null()
..
end type
.. ! However in main program
type(surface), allocatable, target, save :: s !<-- all objects in main program have SAVE by default; shown here for clarity
type(container) :: c
..
c%wall => s
.. ! somewhere in compute section of the code
..
type(wall_function) :: layer
..
layer%wall => c%wall