As far as I know, both of these methods should work:
Use a bootloader (eg, optiboot), but set the lockbits (specifically the bootloader lock bits) to not allow reading the application flash (LPM) from within the bootloader, and pass the -V flag to avrdude to disable verification (since you can't read the code back with the lockbits set to prevent reading). Note that you can't upload directly from the IDE now, since the IDE does not pass the -V and will complain that verification failed when it can't read back the contents of the flash.
Do not use a bootloader, set the lockbits to not allow programming or verification of the flash. This will not interfere with reprogramming it, since the chip erase command needed to erase the flash before writing the new .hex file also clears the lockbits (they will need to be set again in the command you use to write the new sketch). This will also not work for uploading via the IDE, because it does not know to set the lockbits.
Both of these options could be made to work with the IDE by providing a custom hardware package with a modified platform.txt to set these bits.
HOWEVER, none of these solutions prevents the user from examining the .hex file, which contains the data to be written to the flash (though it's not possible to turn the .hex back into readable c code - only to copy the .hex onto other parts, which they could do anyway) - you'd need to build a modified version of avrdude to decrypt the .hex file, but then they could either extract the key from your modified avrdude, or by snooping on the serial data as it goes over the wire.
To truly secure it, you'd need a bootloader that could handle the decryption of the code you send it, so it would never be present unencrypted anywhere other than the flash of the target microcontroller. Writing such a bootloader would be a significant undertaking, especially considering the limited flash and ram (most encryption algorithms require a large amount of ram and program memory to implement relative to the resources on small low end microcontrollers)
AFAIK, the ESP's are hopeless if you need code security, as the user could always take a heat-gun to the board, remove the metal RF shield (if present), and then the flash chip itself, and connect the flash chip to their own reader and dump the contents.
As noted above, there are few plausible cases whereby someone who needs to ask for help on how to prevent people from copying their code is going to be able to write code worth stealing.
Remember that it is not possible to turn a hex file back into readable c code (only "xerox" it onto another chip); if there is something truly magical on the flash that they desperately need (ex, an encryption key), a dedicated adversary with intimate knowledge of AVR instruction set could, with much effort, locate the necessary data in the hex and extract it. And against a sufficiently well-resourced adversary, all code protection efforts are futile - there are actually companies that specialize in breaking code protection and extracting the data, using tricks like glitching the clock and voltage, dissolving the case with fuming nitric acid directly interacting with the die, and so on. But the amount of effort involved is generally greater than the effort that would be required to reimplement the functionality of the sketch themselves (recall that people able to do that would also be true experts). Like all security measures, you can't make breaking the security impossible, only so hard it's not worth doing.